diff options
| author | Frédéric Desbiens <[email protected]> | 2026-09-15 16:47:08 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-09-15 16:47:08 -0400 |
| commit | f5e59a1dd3cd2bdbc6214d408253a089af75a967 (patch) | |
| tree | 6efb06d7cc7b5d5bd37ab75590b79d8017d74899 | |
| parent | 30b3d22adceb10221de7ac3b4c4fd7ebcd3117e1 (diff) | |
Completed Windows simulator support and regression coverage (#736)
Completed and stabilized the Win32 and Win64 MSVC simulator ports.
- Replaced high-latency host synchronization with bounded scheduler handoffs and
critical sections.
- Added a high-resolution timer, tick batching, idle fast-forward, shutdown
coordination and Win64 extension-pointer support.
- Brought the Windows regression tooling and the new thread-transition tests up
on CMake, Ninja and the Visual Studio Build Tools.
- Extended the SMP teardown diagnostics and corrected 64-bit trace-test handling.
This supersedes the historical `win64`, `win32-perf`, `windows-sim-ports` and
`windows-sim-ports-completion` branches; no unmerged change from them is missing
here. The original Win64 port landed in #529.
1,610 of 1,610 tests pass, across five configurations each: Win32 515, Win64 515,
Win64 SMP 580. No external dependency was added, and the existing MSVC warnings
in the trace configuration are unchanged. Hardware validation does not apply to
host simulator ports.
Assisted-by: Codex (gpt-5.6-sol) <[email protected]>
19 files changed, 796 insertions, 211 deletions
diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 655a2f69..4d3e3b50 100644 --- a/ports/win32/vs_2019/inc/tx_port.h +++ b/ports/win32/vs_2019/inc/tx_port.h @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -63,6 +65,7 @@ /* Define compiler library include files. */ +#include <stdint.h> #include <stdlib.h> #include <string.h> @@ -303,9 +306,10 @@ void _tx_initialize_start_interrupts(void); #define TX_THREAD_EXTENSION_0 HANDLE tx_thread_win32_thread_handle; \ DWORD tx_thread_win32_thread_id; \ HANDLE tx_thread_win32_thread_run_semaphore; \ + HANDLE tx_thread_win32_thread_start_semaphore; \ UINT tx_thread_win32_suspension_type; \ UINT tx_thread_win32_int_disabled_flag; -#define TX_THREAD_EXTENSION_1 +#define TX_THREAD_EXTENSION_1 VOID *tx_thread_extension_ptr; #define TX_THREAD_EXTENSION_2 #define TX_THREAD_EXTENSION_3 @@ -361,13 +365,27 @@ void _tx_initialize_start_interrupts(void); #define TX_TIMER_DELETE_EXTENSION(timer_ptr) +/* Store the owning object pointer in the internal timer so timeout handlers can + recover it via a pointer field rather than the ULONG timeout parameter. This + matches the Win64 port and satisfies NetXDuo/USBX default extension macros + that reference tx_timer_internal_extension_ptr / tx_thread_extension_ptr. */ + +#define TX_TIMER_INTERNAL_EXTENSION VOID *tx_timer_internal_extension_ptr; + +#define TX_THREAD_CREATE_TIMEOUT_SETUP(t) (t) -> tx_thread_timer.tx_timer_internal_timeout_function = &(_tx_thread_timeout); \ + (t) -> tx_thread_timer.tx_timer_internal_timeout_param = 0; \ + (t) -> tx_thread_timer.tx_timer_internal_extension_ptr = (VOID *) (t); + +#define TX_THREAD_TIMEOUT_POINTER_SETUP(t) (t) = (TX_THREAD *) _tx_timer_expired_timer_ptr -> tx_timer_internal_extension_ptr; + + struct TX_THREAD_STRUCT; /* Define the Win32 critical section data structure. */ typedef struct TX_WIN32_CRITICAL_SECTION_STRUCT { - HANDLE tx_win32_critical_section_mutex_handle; + CRITICAL_SECTION tx_win32_critical_section_lock; DWORD tx_win32_critical_section_owner; ULONG tx_win32_critical_section_nested_count; } TX_WIN32_CRITICAL_SECTION; @@ -475,24 +493,40 @@ extern CHAR _tx_version_id[]; extern TX_WIN32_CRITICAL_SECTION _tx_win32_critical_section; extern HANDLE _tx_win32_scheduler_semaphore; +extern HANDLE _tx_win32_scheduler_wake_event; extern DWORD _tx_win32_scheduler_id; extern ULONG _tx_win32_global_int_disabled_flag; extern LARGE_INTEGER _tx_win32_time_stamp; extern ULONG _tx_win32_system_error; extern HANDLE _tx_win32_timer_handle; +extern HANDLE _tx_win32_timer_thread_handle; +extern HANDLE _tx_win32_isr_semaphore; extern UINT _tx_win32_timer_id; -extern LARGE_INTEGER _tx_win32_time_stamp; +extern UINT _tx_win32_timer_waiting; +#ifdef TX_WIN32_NO_IDLE_ENABLE +extern HANDLE _tx_win32_timer_kick_event; +#endif + +VOID _tx_win32_scheduler_wake(VOID); + +#ifndef TX_WIN32_USE_HIGH_RESOLUTION_TIMER +#define TX_WIN32_USE_HIGH_RESOLUTION_TIMER 1 +#endif + +#ifndef TX_WIN32_HANDOFF_SPIN_COUNT +#define TX_WIN32_HANDOFF_SPIN_COUNT 64 +#endif #ifndef TX_WIN32_MEMORY_SIZE -#define TX_WIN32_MEMORY_SIZE 64000 +#define TX_WIN32_MEMORY_SIZE 256000 #endif #ifndef TX_TIMER_PERIODIC #ifdef TX_WIN32_SLOW_TIMER #define TX_TIMER_PERIODIC TX_WIN32_SLOW_TIMER #else -#define TX_TIMER_PERIODIC 10 +#define TX_TIMER_PERIODIC 1 #endif #endif diff --git a/ports/win32/vs_2019/src/tx_initialize_low_level.c b/ports/win32/vs_2019/src/tx_initialize_low_level.c index c362b3e7..e531b1ca 100644 --- a/ports/win32/vs_2019/src/tx_initialize_low_level.c +++ b/ports/win32/vs_2019/src/tx_initialize_low_level.c @@ -1,6 +1,6 @@ /*************************************************************************** * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026-present Eclipse ThreadX contributors + * 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 @@ -10,6 +10,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -37,19 +38,40 @@ TX_WIN32_CRITICAL_SECTION _tx_win32_critical_section; HANDLE _tx_win32_scheduler_semaphore; +HANDLE _tx_win32_scheduler_wake_event; DWORD _tx_win32_scheduler_id; ULONG _tx_win32_global_int_disabled_flag; LARGE_INTEGER _tx_win32_time_stamp; ULONG _tx_win32_system_error; +HANDLE _tx_win32_timer_handle; +HANDLE _tx_win32_timer_thread_handle; +HANDLE _tx_win32_isr_semaphore; +UINT _tx_win32_timer_waiting; extern TX_THREAD *_tx_thread_current_ptr; +#ifdef TX_WIN32_NO_IDLE_ENABLE +/* Auto-reset event used by the scheduler to kick the timer thread so that the + simulated clock advances immediately when no thread is ready to run, instead + of waiting for the wall-clock periodic timer. This makes the simulation + CPU-bound rather than wall-clock-bound during idle periods, mirroring the + Linux port's TX_LINUX_NO_IDLE_ENABLE behavior. */ +HANDLE _tx_win32_timer_kick_event; +#endif + +/* Flag set by the atexit handler to stop the timer thread before CRT cleanup + suspends any application threads. Declared volatile so both the main thread + (which sets it) and the timer thread (which reads it) see the change. */ +volatile LONG _tx_win32_exiting = 0; + /* Define simulated timer interrupt. This is done inside a thread, which is how other interrupts may be defined as well. See code below for an example. */ UINT _tx_win32_timer_id; -VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2); +VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2); +static VOID _tx_win32_timer_start(VOID); +static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input); #ifdef TX_WIN32_DEBUG_ENABLE @@ -151,6 +173,10 @@ void _tx_timer_interrupt(void); VOID _tx_initialize_low_level(VOID); VOID _tx_thread_context_save(VOID); VOID _tx_thread_context_restore(VOID); +VOID _tx_win32_scheduler_wake(VOID); + +/* Forward declaration of the process-exit cleanup function. */ +static void _tx_win32_exit_cleanup(void); /* Define other external variable references. */ @@ -186,7 +212,7 @@ extern VOID *_tx_initialize_unused_memory; /* */ /* CALLS */ /* */ -/* CreateMutex Win32 create mutex */ +/* InitializeCriticalSection Win32 initialize lock */ /* CreateThread Win32 create thread */ /* CreateSemaphore Win32 create semaphore */ /* GetCurrentThreadId Win32 get current thread ID */ @@ -226,22 +252,80 @@ VOID _tx_initialize_low_level(VOID) /* Pickup the unique Id of the current thread, which will also be the Id of the scheduler. */ _tx_win32_scheduler_id = GetCurrentThreadId(); - /* Create the system critical section mutex. This is used by the system to block all other access, + /* Initialize the system critical section. This is used by the system to block all other access, analogous to an interrupt lockout on an embedded target. */ - _tx_win32_critical_section.tx_win32_critical_section_mutex_handle = CreateMutex(NULL, FALSE, NULL); + InitializeCriticalSection(&_tx_win32_critical_section.tx_win32_critical_section_lock); _tx_win32_critical_section.tx_win32_critical_section_nested_count = 0; _tx_win32_critical_section.tx_win32_critical_section_owner = 0; /* Create the semaphore that regulates when the scheduler executes. */ _tx_win32_scheduler_semaphore = CreateSemaphore(NULL, 0, 1, NULL); + _tx_win32_isr_semaphore = CreateSemaphore(NULL, 0, 1, NULL); + + /* Create the event that wakes the scheduler whenever the ready state changes. */ + _tx_win32_scheduler_wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (_tx_win32_scheduler_wake_event == NULL) + { + printf("ThreadX Win32 error creating scheduler wake event!\n"); + while(1) + { + } + } + +#ifdef TX_WIN32_NO_IDLE_ENABLE + + /* Create the auto-reset event used to kick the timer thread when the + scheduler detects an idle system (see _tx_thread_schedule). */ + _tx_win32_timer_kick_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (_tx_win32_timer_kick_event == NULL) + { + printf("ThreadX Win32 error creating timer kick event!\n"); + while(1) + { + } + } +#endif /* Initialize the global interrupt disabled flag. */ _tx_win32_global_int_disabled_flag = TX_FALSE; + _tx_win32_timer_waiting = TX_FALSE; /* Done, return to caller. */ } +/* Called by the C runtime during exit() before any CRT cleanup. Sets the + exiting flag so the timer thread and context-save code stop touching + application OS threads (which may hold the CRT heap lock), then forcibly + terminates the timer thread so it cannot fire again during cleanup. */ + +static void _tx_win32_exit_cleanup(void) +{ + + /* Signal all timer-path code to stop. */ + _InterlockedExchange(&_tx_win32_exiting, 1); + + /* Unblock the timer thread if it is waiting on the waitable timer. */ + if (_tx_win32_timer_handle != NULL) + { + CancelWaitableTimer(_tx_win32_timer_handle); + } + + /* Wait up to 50 ms for the timer thread to exit on its own. */ + if (_tx_win32_timer_thread_handle != NULL) + { + if (WaitForSingleObject(_tx_win32_timer_thread_handle, 50) != WAIT_OBJECT_0) + { + + /* Force-terminate if it has not stopped in time. */ + TerminateThread(_tx_win32_timer_thread_handle, 0); + } + _tx_win32_timer_thread_handle = NULL; + } +} + + + /* This routine is called after initialization is complete in order to start all interrupt threads. Interrupt threads in addition to the timer may be added to this routine as well. */ @@ -263,22 +347,161 @@ void _tx_initialize_start_interrupts(void) wTimerRes = min(max(tc.wPeriodMin, TX_TIMER_PERIODIC), tc.wPeriodMax); - /* Start a specified timer event. The timer runs in its own thread. - It calls the specified callback function when the event is activated. */ - _tx_win32_timer_id = timeSetEvent(TX_TIMER_PERIODIC, wTimerRes, _tx_win32_timer_interrupt, 0, TIME_PERIODIC); + /* Request the best available timer resolution for the simulation. */ + if (timeBeginPeriod(wTimerRes) != TIMERR_NOERROR) + { + printf("ThreadX Win32 error configuring timer resolution!\n"); + while (1) + { + } + } + + /* Create the periodic waitable timer used to drive simulated interrupts. */ +#if (TX_WIN32_USE_HIGH_RESOLUTION_TIMER != 0) + _tx_win32_timer_handle = CreateWaitableTimerEx(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + if (_tx_win32_timer_handle == NULL) +#endif + { + _tx_win32_timer_handle = CreateWaitableTimer(NULL, FALSE, NULL); + } + + if (_tx_win32_timer_handle == NULL) + { + printf("ThreadX Win32 error creating timer handle!\n"); + while (1) + { + } + } + + /* Create the timer thread so interrupts are serialized through one execution context. */ + _tx_win32_timer_thread_handle = CreateThread(NULL, 0, _tx_win32_timer_thread_entry, NULL, 0, NULL); + if (_tx_win32_timer_thread_handle == NULL) + { + printf("ThreadX Win32 error creating timer thread!\n"); + while (1) + { + } + } + + SetThreadPriority(_tx_win32_timer_thread_handle, THREAD_PRIORITY_HIGHEST); + + _tx_win32_timer_id = 1; + + /* Register exit cleanup so the timer thread is stopped before CRT cleanup + runs. Without this, exit() can deadlock: the CRT holds the heap lock + while the timer fires and suspends the exiting thread via SuspendThread, + causing any subsequent malloc to block forever. */ + atexit(_tx_win32_exit_cleanup); + + /* Start the first simulated tick. */ + _tx_win32_timer_start(); } /* Define the ThreadX system timer interrupt. Other interrupts may be simulated in a similar way. */ -VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2) +VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) { + TX_PARAMETER_NOT_USED(wTimerID); + TX_PARAMETER_NOT_USED(msg); + TX_PARAMETER_NOT_USED(dwUser); + TX_PARAMETER_NOT_USED(dw1); + TX_PARAMETER_NOT_USED(dw2); + + /* Skip the interrupt entirely if exit() has been called. The CRT heap + lock may be held by the exiting thread; calling SuspendThread() on it + at this point causes a permanent deadlock. */ + if (_tx_win32_exiting) + return; + /* Call ThreadX context save for interrupt preparation. */ _tx_thread_context_save(); - /* Call the ThreadX system timer interrupt processing. */ - _tx_timer_interrupt(); + /* Fire TX_WIN32_TICKS_PER_INTERRUPT ticks inside a single interrupt + context. The SuspendThread/ResumeThread overhead is amortized across + all N ticks, and all timer-based delays shrink by factor N. The + relative ordering of thread wakeups is preserved because each call to + _tx_timer_interrupt() advances the tick counter by exactly one step and + processes the timers that expire at that step. */ +#ifndef TX_WIN32_TICKS_PER_INTERRUPT +#define TX_WIN32_TICKS_PER_INTERRUPT 5 +#endif + { + UINT _tick_i; + for (_tick_i = 0; _tick_i < TX_WIN32_TICKS_PER_INTERRUPT; _tick_i++) + _tx_timer_interrupt(); + } /* Call ThreadX context restore for interrupt completion. */ _tx_thread_context_restore(); + + /* Wake the scheduler so it can promptly observe timer-driven work. */ + _tx_win32_scheduler_wake(); +} + + +static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input) +{ + TX_PARAMETER_NOT_USED(thread_input); + + /* Drive periodic simulated interrupts from a single thread. + Exit the loop when _tx_win32_exiting is set by the atexit handler. */ + while (!_tx_win32_exiting) + { +#ifdef TX_WIN32_NO_IDLE_ENABLE + + /* Wake either on the periodic wall-clock timer or on a scheduler kick + (issued when the system is idle). Firing on the kick advances the + simulated clock immediately, without waiting for the wall clock. */ + HANDLE _wait_handles[2]; + + _wait_handles[0] = _tx_win32_timer_handle; + _wait_handles[1] = _tx_win32_timer_kick_event; + if (WaitForMultipleObjects(2, _wait_handles, FALSE, INFINITE) == WAIT_FAILED) + break; +#else + if (WaitForSingleObject(_tx_win32_timer_handle, INFINITE) != WAIT_OBJECT_0) + break; +#endif + if (_tx_win32_exiting) + break; + _tx_win32_timer_interrupt(0, 0, 0, 0, 0); + if (_tx_win32_exiting) + break; + _tx_win32_timer_start(); + } + return 0; +} + + +VOID _tx_win32_scheduler_wake(VOID) +{ + + /* Wake the scheduler if it is waiting for a state change. */ + if (_tx_win32_scheduler_wake_event != NULL) + { + SetEvent(_tx_win32_scheduler_wake_event); + } +} + + +static VOID _tx_win32_timer_start(VOID) +{ + +LARGE_INTEGER due_time; + + + /* Rearm the host timer relative to "now" to avoid burst catch-up ticks. */ + due_time.QuadPart = -(((LONGLONG) TX_TIMER_PERIODIC) * 10000LL); +#if (TX_WIN32_USE_HIGH_RESOLUTION_TIMER != 0) + if (SetWaitableTimerEx(_tx_win32_timer_handle, &due_time, 0, NULL, NULL, NULL, 0) == 0) +#else + if (SetWaitableTimer(_tx_win32_timer_handle, &due_time, 0, NULL, NULL, FALSE) == 0) +#endif + { + printf("ThreadX Win32 error starting timer!\n"); + while (1) + { + } + } } diff --git a/ports/win32/vs_2019/src/tx_thread_context_restore.c b/ports/win32/vs_2019/src/tx_thread_context_restore.c index 36fa1e4b..ae559bed 100644 --- a/ports/win32/vs_2019/src/tx_thread_context_restore.c +++ b/ports/win32/vs_2019/src/tx_thread_context_restore.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -70,6 +72,7 @@ /**************************************************************************/ VOID _tx_thread_context_restore(VOID) { +TX_THREAD *execute_thread; /* Enter critical section to ensure other threads are not playing with the core ThreadX data structures. */ @@ -81,6 +84,9 @@ VOID _tx_thread_context_restore(VOID) /* Decrement the nested interrupt count. */ _tx_thread_system_state--; + /* Pickup the execute thread pointer. */ + execute_thread = _tx_thread_execute_ptr; + /* Determine if this is the first nested interrupt and if a ThreadX application thread was running at the time. */ if ((!_tx_thread_system_state) && (_tx_thread_current_ptr)) @@ -109,8 +115,32 @@ VOID _tx_thread_context_restore(VOID) /* Clear the current thread pointer. */ _tx_thread_current_ptr = TX_NULL; + /* Block the timer ISR until the resumed thread has observed the wakeup. */ + _tx_win32_timer_waiting = TX_TRUE; + /* Wakeup the system thread by setting the system semaphore. */ ReleaseSemaphore(_tx_win32_scheduler_semaphore, 1, NULL); + _tx_win32_scheduler_wake(); + + /* If the timer made a solicited wakeup ready, let that thread run before + the host timer ISR continues. */ + if ((execute_thread != TX_NULL) && + (execute_thread -> tx_thread_win32_suspension_type == 0)) + { + + /* Spin-poll for the scheduler to complete the solicited wakeup + before the timer ISR proceeds. */ + _tx_win32_critical_section_release_all(&_tx_win32_critical_section); + while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); + _tx_win32_critical_section_obtain(&_tx_win32_critical_section); + while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) == WAIT_OBJECT_0) + { + } + } + + /* The timer ISR no longer needs to hold off future ticks. */ + _tx_win32_timer_waiting = TX_FALSE; } else { @@ -119,8 +149,31 @@ VOID _tx_thread_context_restore(VOID) ResumeThread(_tx_thread_current_ptr -> tx_thread_win32_thread_handle); } } + else if ((!_tx_thread_system_state) && (execute_thread != TX_NULL)) + { + + /* The timer made a thread ready while the scheduler was idle. Keep the + timer ISR blocked until the solicited wakeup has started running. */ + _tx_win32_timer_waiting = TX_TRUE; + _tx_win32_scheduler_wake(); + + if (execute_thread -> tx_thread_win32_suspension_type == 0) + { + + /* Spin-poll for the scheduler to hand off to the next thread and + acknowledge via the ISR semaphore. Keeps timer-path latency low. */ + _tx_win32_critical_section_release_all(&_tx_win32_critical_section); + while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); + _tx_win32_critical_section_obtain(&_tx_win32_critical_section); + while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) == WAIT_OBJECT_0) + { + } + } + + _tx_win32_timer_waiting = TX_FALSE; + } /* Leave Win32 critical section. */ _tx_win32_critical_section_release_all(&_tx_win32_critical_section); } - diff --git a/ports/win32/vs_2019/src/tx_thread_context_save.c b/ports/win32/vs_2019/src/tx_thread_context_save.c index 2f4afb40..4574b827 100644 --- a/ports/win32/vs_2019/src/tx_thread_context_save.c +++ b/ports/win32/vs_2019/src/tx_thread_context_save.c @@ -1,6 +1,6 @@ /*************************************************************************** * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026-present Eclipse ThreadX contributors + * 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 @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -30,6 +32,11 @@ #include "tx_thread.h" #include "tx_timer.h" +/* Set to non-zero by the atexit handler in tx_initialize_low_level.c when the + process is calling exit(). Prevents SuspendThread() from being called on + a thread that may be holding the CRT heap lock. */ +extern volatile LONG _tx_win32_exiting; + /**************************************************************************/ /* */ @@ -86,6 +93,15 @@ TX_THREAD *thread_ptr; if ((thread_ptr) && (_tx_thread_system_state == 0)) { + /* Skip if the process is calling exit(). Suspending a thread that + holds the CRT heap lock causes a permanent deadlock because any + subsequent malloc in another thread will block forever. */ + if (_tx_win32_exiting) + { + _tx_win32_critical_section_release(&_tx_win32_critical_section); + return; + } + /* Yes, this is the first interrupt and an application thread is running... suspend it! */ @@ -104,4 +120,3 @@ TX_THREAD *thread_ptr; /* Exit Win32 critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); } - diff --git a/ports/win32/vs_2019/src/tx_thread_interrupt_control.c b/ports/win32/vs_2019/src/tx_thread_interrupt_control.c index 019aabef..ca73b7fb 100644 --- a/ports/win32/vs_2019/src/tx_thread_interrupt_control.c +++ b/ports/win32/vs_2019/src/tx_thread_interrupt_control.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -93,8 +95,6 @@ UINT _tx_thread_interrupt_control(UINT new_posture) { UINT old_posture; -HANDLE threadhandle; -int threadpriority; DWORD threadid; TX_THREAD *thread_ptr; @@ -119,22 +119,16 @@ TX_THREAD *thread_ptr; /* Determine if the thread was terminated. */ - /* Pickup the handle of the current thread. */ - threadhandle = GetCurrentThread(); - /* Pickup the current thread pointer. */ thread_ptr = _tx_thread_current_ptr; - /* Pickup the priority of the current thread. */ - threadpriority = GetThreadPriority(threadhandle); - /* Pickup the ID of the current thread. */ threadid = GetCurrentThreadId(); /* Determine if this is a thread (THREAD_PRIORITY_LOWEST) and it does not match the current thread pointer. */ - if ((threadpriority == THREAD_PRIORITY_LOWEST) && - ((!thread_ptr) || (thread_ptr -> tx_thread_win32_thread_id != threadid))) + if (((!thread_ptr) || (thread_ptr -> tx_thread_win32_thread_id != threadid)) && + (GetThreadPriority(GetCurrentThread()) == THREAD_PRIORITY_LOWEST)) { /* This indicates the Win32 thread was actually terminated by ThreadX is only @@ -204,4 +198,3 @@ TX_THREAD *thread_ptr; /* Return the previous interrupt disable posture. */ return(old_posture); } - diff --git a/ports/win32/vs_2019/src/tx_thread_schedule.c b/ports/win32/vs_2019/src/tx_thread_schedule.c index d67efee8..1fa11c59 100644 --- a/ports/win32/vs_2019/src/tx_thread_schedule.c +++ b/ports/win32/vs_2019/src/tx_thread_schedule.c @@ -1,6 +1,6 @@ /*************************************************************************** * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026-present Eclipse ThreadX contributors + * 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 @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -31,6 +33,9 @@ #include "tx_timer.h" +static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle); + + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -59,7 +64,6 @@ /* */ /* ReleaseSemaphore Win32 release semaphore */ /* ResumeThread Win32 resume thread */ -/* Sleep Win32 thread sleep */ /* WaitForSingleObject Win32 wait on a semaphore */ /* _tx_win32_critical_section_obtain Obtain critical section */ /* _tx_win32_critical_section_release Release critical section */ @@ -71,6 +75,8 @@ /**************************************************************************/ VOID _tx_thread_schedule(VOID) { +DWORD wait_status; + /* Loop forever. */ @@ -102,8 +108,27 @@ VOID _tx_thread_schedule(VOID) /* Leave the critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); - /* Now sleep so we don't block forever. */ - Sleep(2); +#ifdef TX_WIN32_NO_IDLE_ENABLE + + /* No thread is ready to run: advance the simulated clock now + instead of waiting for the wall-clock periodic timer. Kick + the timer thread so it fires the next tick(s) immediately, + then wait for it to signal progress via the wake event. A + short bounded timeout guards against a missed wake. This + makes idle periods CPU-bound rather than wall-clock-bound, + mirroring the Linux port's TX_LINUX_NO_IDLE_ENABLE path. */ + SetEvent(_tx_win32_timer_kick_event); + WaitForSingleObject(_tx_win32_scheduler_wake_event, 2); +#else + + /* Yield to other threads (timer, application threads) instead of + blocking indefinitely. This spin-poll eliminates the ~0.5 ms + kernel-wake latency of WaitForSingleObject(INFINITE) and reduces + context-switch overhead by >10x at the cost of higher CPU usage + during test runs. Drain any pending wake event to keep it clean. */ + WaitForSingleObject(_tx_win32_scheduler_wake_event, 0); + SwitchToThread(); +#endif } } @@ -136,8 +161,38 @@ VOID _tx_thread_schedule(VOID) /* Debug entry. */ _tx_win32_debug_entry_insert("SCHEDULE-release_sem", __FILE__, __LINE__); + /* Clear any stale wakeup acknowledgements before this solicited resume. */ + _tx_win32_semaphore_reset(_tx_thread_current_ptr -> tx_thread_win32_thread_start_semaphore); + _tx_win32_semaphore_reset(_tx_thread_current_ptr -> tx_thread_win32_thread_run_semaphore); + /* Let the thread run again by releasing its run semaphore. */ - ReleaseSemaphore(_tx_thread_current_ptr -> tx_thread_win32_thread_run_semaphore, 1, NULL); + if (ReleaseSemaphore(_tx_thread_current_ptr -> tx_thread_win32_thread_run_semaphore, 1, NULL) == 0) + { + + /* Increment the system error counter. */ + _tx_win32_system_error++; + } + + /* Let the solicited wakeup reach ThreadX before the timer ISR advances again. */ + if (_tx_win32_timer_waiting) + { + + /* Wait for the thread to acknowledge the wakeup and then release the ISR. */ + wait_status = WaitForSingleObject(_tx_thread_current_ptr -> tx_thread_win32_thread_start_semaphore, INFINITE); + if (ReleaseSemaphore(_tx_win32_isr_semaphore, 1, NULL) == 0) + { + + /* Increment the system error counter. */ + _tx_win32_system_error++; + } + + if (wait_status != WAIT_OBJECT_0) + { + + /* Increment the system error counter. */ + _tx_win32_system_error++; + } + } } /* Debug entry. */ @@ -146,20 +201,29 @@ VOID _tx_thread_schedule(VOID) /* Exit Win32 critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); - /* Now suspend the main thread so the application thread can run. */ - WaitForSingleObject(_tx_win32_scheduler_semaphore, INFINITE); + /* Spin-poll for the application thread to return control. Using + SwitchToThread() between polls yields the CPU without blocking, + matching the low-latency approach used in the idle loop above. */ + while (WaitForSingleObject(_tx_win32_scheduler_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); } } -/* Define the ThreadX Win32 critical section get, release, and release all functions. */ - -void _tx_win32_critical_section_obtain(TX_WIN32_CRITICAL_SECTION *critical_section) +static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle) { -TX_THREAD *thread_ptr; + /* Drain any stale semaphore count from a previous host-side wakeup. */ + while (WaitForSingleObject(semaphore_handle, 0) == WAIT_OBJECT_0) + { + } +} +/* Define the ThreadX Win32 critical section get, release, and release all functions. */ + +void _tx_win32_critical_section_obtain(TX_WIN32_CRITICAL_SECTION *critical_section) +{ /* Is the protection owned? */ if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId()) { @@ -170,15 +234,10 @@ TX_THREAD *thread_ptr; else { - /* Pickup the current thread pointer. */ - thread_ptr = _tx_thread_current_ptr; - /* Get the Win32 critical section. */ - while (WaitForSingleObject(critical_section -> tx_win32_critical_section_mutex_handle, 3) != WAIT_OBJECT_0) - { - } + EnterCriticalSection(&critical_section -> tx_win32_critical_section_lock); - /* At this point we have the mutex. */ + /* At this point we have the critical section. */ /* Increment the nesting counter. */ critical_section -> tx_win32_critical_section_nested_count = 1; @@ -193,7 +252,7 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s { - /* Ensure the caller is the mutex owner. */ + /* Ensure the caller is the critical section owner. */ if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId()) { @@ -211,25 +270,9 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s /* Yes, it is being released clear the owner. */ critical_section -> tx_win32_critical_section_owner = 0; - /* Finally, release the mutex. */ - if (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) != TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } - - /* Just in case, make sure there the mutex is not owned. */ - while (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) == TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } - - /* Sleep for 0, just to relinquish to other ready threads. */ - Sleep(0); - } + /* Finally, release the critical section. */ + LeaveCriticalSection(&critical_section -> tx_win32_critical_section_lock); + } } } else @@ -244,7 +287,7 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *critical_section) { - /* Ensure the caller is the mutex owner. */ + /* Ensure the caller is the critical section owner. */ if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId()) { @@ -258,21 +301,8 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri /* Yes, it is being release clear the owner. */ critical_section -> tx_win32_critical_section_owner = 0; - /* Finally, release the mutex. */ - if (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) != TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } - - /* Just in case, make sure there the mutex is not owned. */ - while (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) == TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } + /* Finally, release the critical section. */ + LeaveCriticalSection(&critical_section -> tx_win32_critical_section_lock); } } else @@ -282,4 +312,3 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri _tx_win32_system_error++; } } - diff --git a/ports/win32/vs_2019/src/tx_thread_stack_build.c b/ports/win32/vs_2019/src/tx_thread_stack_build.c index ccbabefd..f473ec95 100644 --- a/ports/win32/vs_2019/src/tx_thread_stack_build.c +++ b/ports/win32/vs_2019/src/tx_thread_stack_build.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). // Some portions generated by Copilot (Opus 5). @@ -114,6 +115,20 @@ ALIGN_TYPE fake_stack_ptr; } } + /* Create the scheduler acknowledgement semaphore for this thread. */ + thread_ptr -> tx_thread_win32_thread_start_semaphore = CreateSemaphore(NULL, 0, 1, NULL); + + /* Determine if the start semaphore was created successfully. */ + if (!thread_ptr -> tx_thread_win32_thread_start_semaphore) + { + + /* Display an error message. */ + printf("ThreadX Win32 error creating thread start semaphore!\n"); + while(1) + { + } + } + /* Setup the thread suspension type to solicited thread suspension. Pseudo interrupt handlers will suspend with this field set to 1. */ thread_ptr -> tx_thread_win32_suspension_type = 0; @@ -136,24 +151,77 @@ ALIGN_TYPE fake_stack_ptr; /* Make the thread initially ready so it will run to the initial wait on its run semaphore. */ ResumeThread(thread_ptr -> tx_thread_win32_thread_handle); + + /* Wait until the host thread is parked at the controlled handoff point + before ThreadX can schedule it. */ + if (WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_start_semaphore, INFINITE) != WAIT_OBJECT_0) + { + + /* Display an error message. */ + printf("ThreadX Win32 error synchronizing thread startup!\n"); + while(1) + { + } + } } DWORD WINAPI _tx_win32_thread_entry(LPVOID ptr) { -TX_THREAD *thread_ptr; +TX_THREAD *thread_ptr; +TX_THREAD *current_thread_ptr; +HANDLE threadhandle; +int threadpriority; +DWORD threadid; +ULONG handoff_spin_count; /* Pickup the current thread pointer. */ thread_ptr = (TX_THREAD *) ptr; - /* Now suspend the thread initially. If the thread has already - been scheduled, this will return immediately. */ - WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, INFINITE); + /* Tell the creator that this host thread has reached the controlled + handoff point and is ready to be scheduled. */ + ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); + + /* Spin briefly for the scheduler to release this thread to run, then + block so dormant threads do not consume host CPU indefinitely. */ + handoff_spin_count = TX_WIN32_HANDOFF_SPIN_COUNT; + while (WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, 0) != WAIT_OBJECT_0) + { + if (handoff_spin_count != 0) + { + handoff_spin_count--; + SwitchToThread(); + } + else + { + WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, INFINITE); + break; + } + } + + /* Acknowledge that the host thread is now able to execute ThreadX code. */ + ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); + + /* A deleted host thread can be released only to let it exit. In notify-enabled + builds, the first TX_DISABLE in _tx_thread_shell_entry catches this path. + When callbacks are disabled, perform the same check before the shell calls + the stale ThreadX entry function. */ + _tx_win32_critical_section_obtain(&_tx_win32_critical_section); + threadhandle = GetCurrentThread(); + threadpriority = GetThreadPriority(threadhandle); + threadid = GetCurrentThreadId(); + current_thread_ptr = _tx_thread_current_ptr; + if ((threadpriority == THREAD_PRIORITY_LOWEST) && + ((current_thread_ptr == TX_NULL) || (current_thread_ptr -> tx_thread_win32_thread_id != threadid))) + { + _tx_win32_critical_section_release_all(&_tx_win32_critical_section); + ExitThread(0); + } + _tx_win32_critical_section_release(&_tx_win32_critical_section); /* Call ThreadX thread entry point. */ _tx_thread_shell_entry(); return EXIT_SUCCESS; } - diff --git a/ports/win32/vs_2019/src/tx_thread_system_return.c b/ports/win32/vs_2019/src/tx_thread_system_return.c index 60176135..52f65b4f 100644 --- a/ports/win32/vs_2019/src/tx_thread_system_return.c +++ b/ports/win32/vs_2019/src/tx_thread_system_return.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -83,6 +85,7 @@ UINT temp_thread_state; HANDLE threadhandle; int threadpriority; DWORD threadid; +ULONG handoff_spin_count; /* Enter Win32 critical section. */ @@ -149,6 +152,7 @@ DWORD threadid; on. Note that the main scheduling algorithm will take care of setting the current thread pointer to NULL. */ ReleaseSemaphore(_tx_win32_scheduler_semaphore, 1, NULL); + _tx_win32_scheduler_wake(); /* Leave Win32 critical section. */ _tx_win32_critical_section_release_all(&_tx_win32_critical_section); @@ -161,9 +165,25 @@ DWORD threadid; ExitThread(0); } - /* Wait on the run semaphore for this thread. This won't get set again - until the thread is scheduled. */ - WaitForSingleObject(temp_run_semaphore, INFINITE); + /* Spin briefly for the scheduler to grant this thread a new time-slice, + then block so suspended threads do not consume host CPU indefinitely. */ + handoff_spin_count = TX_WIN32_HANDOFF_SPIN_COUNT; + while (WaitForSingleObject(temp_run_semaphore, 0) != WAIT_OBJECT_0) + { + if (handoff_spin_count != 0) + { + handoff_spin_count--; + SwitchToThread(); + } + else + { + WaitForSingleObject(temp_run_semaphore, INFINITE); + break; + } + } + + /* Acknowledge that the thread is once again executing ThreadX code. */ + ReleaseSemaphore(temp_thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); /* Enter Win32 critical section. */ _tx_win32_critical_section_obtain(&_tx_win32_critical_section); @@ -203,4 +223,3 @@ DWORD threadid; _tx_win32_critical_section_release(&_tx_win32_critical_section); } } - diff --git a/ports/win64/vs_2022/inc/tx_port.h b/ports/win64/vs_2022/inc/tx_port.h index eeabf651..2b486ed9 100644 --- a/ports/win64/vs_2022/inc/tx_port.h +++ b/ports/win64/vs_2022/inc/tx_port.h @@ -13,6 +13,8 @@ * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ @@ -167,6 +169,7 @@ extern TEST_FLAG test_forced_mutex_timeout; back changes structures the application also sees. This port and the win32 one were the only two that did. */ + /* Define ThreadX basic types for this port. */ #define VOID void @@ -331,7 +334,7 @@ void _tx_initialize_start_interrupts(void); HANDLE tx_thread_win32_thread_start_semaphore; \ UINT tx_thread_win32_suspension_type; \ UINT tx_thread_win32_int_disabled_flag; -#define TX_THREAD_EXTENSION_1 +#define TX_THREAD_EXTENSION_1 VOID *tx_thread_extension_ptr; #define TX_THREAD_EXTENSION_2 #define TX_THREAD_EXTENSION_3 @@ -404,7 +407,7 @@ struct TX_THREAD_STRUCT; typedef struct TX_WIN32_CRITICAL_SECTION_STRUCT { - HANDLE tx_win32_critical_section_mutex_handle; + CRITICAL_SECTION tx_win32_critical_section_lock; DWORD tx_win32_critical_section_owner; ULONG tx_win32_critical_section_nested_count; } TX_WIN32_CRITICAL_SECTION; @@ -565,21 +568,29 @@ extern HANDLE _tx_win32_timer_handle; extern HANDLE _tx_win32_timer_thread_handle; extern HANDLE _tx_win32_isr_semaphore; extern UINT _tx_win32_timer_waiting; +#ifdef TX_WIN32_NO_IDLE_ENABLE +extern HANDLE _tx_win32_timer_kick_event; +#endif extern UINT _tx_win32_timer_id; extern LARGE_INTEGER _tx_win32_time_stamp; #ifndef TX_WIN32_MEMORY_SIZE -#define TX_WIN32_MEMORY_SIZE 64000 +#define TX_WIN32_MEMORY_SIZE 256000 #endif VOID _tx_win32_scheduler_wake(VOID); +/* This simulation port is not for production use. Run at 1 ms per tick + (10x faster than wall clock at the default 100 ticks/second) so that + regression tests with protocol timeouts complete in a fraction of real + time without changing any tick-count-based test logic. The slower + TX_WIN32_SLOW_TIMER escape hatch is preserved for debugging. */ #ifndef TX_TIMER_PERIODIC #ifdef TX_WIN32_SLOW_TIMER #define TX_TIMER_PERIODIC TX_WIN32_SLOW_TIMER #else -#define TX_TIMER_PERIODIC 10 +#define TX_TIMER_PERIODIC 1 #endif #endif @@ -596,5 +607,3 @@ VOID _tx_win32_scheduler_wake(VOID); - - diff --git a/ports/win64/vs_2022/src/tx_initialize_low_level.c b/ports/win64/vs_2022/src/tx_initialize_low_level.c index b7e96575..b41477d7 100644 --- a/ports/win64/vs_2022/src/tx_initialize_low_level.c +++ b/ports/win64/vs_2022/src/tx_initialize_low_level.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ /** */ @@ -51,6 +52,15 @@ HANDLE _tx_win32_isr_semaphore; UINT _tx_win32_timer_waiting; extern TX_THREAD *_tx_thread_current_ptr; +#ifdef TX_WIN32_NO_IDLE_ENABLE +/* Auto-reset event used by the scheduler to kick the timer thread so that the + simulated clock advances immediately when no thread is ready to run, instead + of waiting for the wall-clock periodic timer. This makes the simulation + CPU-bound rather than wall-clock-bound during idle periods, mirroring the + Linux port's TX_LINUX_NO_IDLE_ENABLE behavior. */ +HANDLE _tx_win32_timer_kick_event; +#endif + /* Define simulated timer interrupt. This is done inside a thread, which is how other interrupts may be defined as well. See code below for an @@ -197,7 +207,7 @@ extern VOID *_tx_initialize_unused_memory; /* */ /* CALLS */ /* */ -/* CreateMutex Win32 create mutex */ +/* InitializeCriticalSection Win32 initialize lock */ /* CreateThread Win32 create thread */ /* CreateSemaphore Win32 create semaphore */ /* GetCurrentThreadId Win32 get current thread ID */ @@ -237,18 +247,11 @@ VOID _tx_initialize_low_level(VOID) /* Pickup the unique Id of the current thread, which will also be the Id of the scheduler. */ _tx_win32_scheduler_id = GetCurrentThreadId(); - /* Create the system critical section mutex. This is used by the system to block all other access, + /* Initialize the system critical section. This is used by the system to block all other access, analogous to an interrupt lockout on an embedded target. */ - _tx_win32_critical_section.tx_win32_critical_section_mutex_handle = CreateMutex(NULL, FALSE, NULL); + InitializeCriticalSection(&_tx_win32_critical_section.tx_win32_critical_section_lock); _tx_win32_critical_section.tx_win32_critical_section_nested_count = 0; _tx_win32_critical_section.tx_win32_critical_section_owner = 0; - if (_tx_win32_critical_section.tx_win32_critical_section_mutex_handle == NULL) - { - printf("ThreadX Win64 error creating critical section mutex!\n"); - while(1) - { - } - } /* Create the semaphore that regulates when the scheduler executes. */ _tx_win32_scheduler_semaphore = CreateSemaphore(NULL, 0, 1, NULL); @@ -271,6 +274,20 @@ VOID _tx_initialize_low_level(VOID) } } +#ifdef TX_WIN32_NO_IDLE_ENABLE + + /* Create the auto-reset event used to kick the timer thread when the + scheduler detects an idle system (see _tx_thread_schedule). */ + _tx_win32_timer_kick_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (_tx_win32_timer_kick_event == NULL) + { + printf("ThreadX Win64 error creating timer kick event!\n"); + while(1) + { + } + } +#endif + /* Initialize the global interrupt disabled flag. */ _tx_win32_global_int_disabled_flag = TX_FALSE; _tx_win32_timer_waiting = TX_FALSE; @@ -358,8 +375,20 @@ VOID CALLBACK _tx_win32_timer_interrupt(UINT wTimerID, UINT msg, DWORD_PTR dwUse /* Call ThreadX context save for interrupt preparation. */ _tx_thread_context_save(); - /* Call the ThreadX system timer interrupt processing. */ - _tx_timer_interrupt(); + /* Fire TX_WIN32_TICKS_PER_INTERRUPT ticks inside a single interrupt + context. The SuspendThread/ResumeThread overhead is amortized across + all N ticks, and all timer-based delays shrink by factor N. The + relative ordering of thread wakeups is preserved because each call to + _tx_timer_interrupt() advances the tick counter by exactly one step and + processes the timers that expire at that step. */ +#ifndef TX_WIN32_TICKS_PER_INTERRUPT +#define TX_WIN32_TICKS_PER_INTERRUPT 5 +#endif + { + UINT _tick_i; + for (_tick_i = 0; _tick_i < TX_WIN32_TICKS_PER_INTERRUPT; _tick_i++) + _tx_timer_interrupt(); + } /* Call ThreadX context restore for interrupt completion. */ _tx_thread_context_restore(); @@ -376,13 +405,26 @@ static DWORD WINAPI _tx_win32_timer_thread_entry(LPVOID thread_input) /* Drive periodic simulated interrupts from a single thread. */ while (1) { +#ifdef TX_WIN32_NO_IDLE_ENABLE + + /* Wake either on the periodic wall-clock timer or on a scheduler kick + (issued when the system is idle). Firing on the kick advances the + simulated clock immediately, without waiting for the wall clock. */ + HANDLE _wait_handles[2]; + + _wait_handles[0] = _tx_win32_timer_handle; + _wait_handles[1] = _tx_win32_timer_kick_event; + WaitForMultipleObjects(2, _wait_handles, FALSE, INFINITE); +#else WaitForSingleObject(_tx_win32_timer_handle, INFINITE); +#endif _tx_win32_timer_interrupt(0, 0, 0, 0, 0); _tx_win32_timer_start(); } } + VOID _tx_win32_scheduler_wake(VOID) { @@ -414,4 +456,3 @@ LARGE_INTEGER due_time; } } } - diff --git a/ports/win64/vs_2022/src/tx_thread_context_restore.c b/ports/win64/vs_2022/src/tx_thread_context_restore.c index 9786e7cb..f860d61f 100644 --- a/ports/win64/vs_2022/src/tx_thread_context_restore.c +++ b/ports/win64/vs_2022/src/tx_thread_context_restore.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -131,9 +132,11 @@ TX_THREAD *execute_thread; (execute_thread -> tx_thread_win32_suspension_type == 0)) { - /* Release the critical section while the scheduler runs. */ + /* Spin-poll for the scheduler to complete the solicited wakeup + before the timer ISR proceeds. */ _tx_win32_critical_section_release_all(&_tx_win32_critical_section); - WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); _tx_win32_critical_section_obtain(&_tx_win32_critical_section); while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) == WAIT_OBJECT_0) { @@ -161,9 +164,11 @@ TX_THREAD *execute_thread; if (execute_thread -> tx_thread_win32_suspension_type == 0) { - /* Release the critical section while the scheduler runs. */ + /* Spin-poll for the scheduler to hand off to the next thread and + acknowledge via the ISR semaphore. Keeps timer-path latency low. */ _tx_win32_critical_section_release_all(&_tx_win32_critical_section); - WaitForSingleObject(_tx_win32_isr_semaphore, INFINITE); + while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); _tx_win32_critical_section_obtain(&_tx_win32_critical_section); while (WaitForSingleObject(_tx_win32_isr_semaphore, 0) == WAIT_OBJECT_0) { @@ -177,4 +182,3 @@ TX_THREAD *execute_thread; _tx_win32_critical_section_release_all(&_tx_win32_critical_section); } - diff --git a/ports/win64/vs_2022/src/tx_thread_interrupt_control.c b/ports/win64/vs_2022/src/tx_thread_interrupt_control.c index ab50a3c9..b38a0749 100644 --- a/ports/win64/vs_2022/src/tx_thread_interrupt_control.c +++ b/ports/win64/vs_2022/src/tx_thread_interrupt_control.c @@ -13,6 +13,8 @@ * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). + /**************************************************************************/ /**************************************************************************/ /** */ @@ -96,8 +98,6 @@ UINT _tx_thread_interrupt_control(UINT new_posture) { UINT old_posture; -HANDLE threadhandle; -int threadpriority; DWORD threadid; TX_THREAD *thread_ptr; @@ -122,22 +122,16 @@ TX_THREAD *thread_ptr; /* Determine if the thread was terminated. */ - /* Pickup the handle of the current thread. */ - threadhandle = GetCurrentThread(); - /* Pickup the current thread pointer. */ thread_ptr = _tx_thread_current_ptr; - /* Pickup the priority of the current thread. */ - threadpriority = GetThreadPriority(threadhandle); - /* Pickup the ID of the current thread. */ threadid = GetCurrentThreadId(); /* Determine if this is a thread (THREAD_PRIORITY_LOWEST) and it does not match the current thread pointer. */ - if ((threadpriority == THREAD_PRIORITY_LOWEST) && - ((!thread_ptr) || (thread_ptr -> tx_thread_win32_thread_id != threadid))) + if (((!thread_ptr) || (thread_ptr -> tx_thread_win32_thread_id != threadid)) && + (GetThreadPriority(GetCurrentThread()) == THREAD_PRIORITY_LOWEST)) { /* This indicates the Win32 thread was actually terminated by ThreadX is only @@ -208,4 +202,3 @@ TX_THREAD *thread_ptr; return(old_posture); } - diff --git a/ports/win64/vs_2022/src/tx_thread_schedule.c b/ports/win64/vs_2022/src/tx_thread_schedule.c index d08305ee..f6bef949 100644 --- a/ports/win64/vs_2022/src/tx_thread_schedule.c +++ b/ports/win64/vs_2022/src/tx_thread_schedule.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -111,8 +112,27 @@ DWORD wait_status; /* Leave the critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); - /* Wait for the next scheduling state change. */ - WaitForSingleObject(_tx_win32_scheduler_wake_event, INFINITE); +#ifdef TX_WIN32_NO_IDLE_ENABLE + + /* No thread is ready to run: advance the simulated clock now + instead of waiting for the wall-clock periodic timer. Kick + the timer thread so it fires the next tick(s) immediately, + then wait for it to signal progress via the wake event. A + short bounded timeout guards against a missed wake. This + makes idle periods CPU-bound rather than wall-clock-bound, + mirroring the Linux port's TX_LINUX_NO_IDLE_ENABLE path. */ + SetEvent(_tx_win32_timer_kick_event); + WaitForSingleObject(_tx_win32_scheduler_wake_event, 2); +#else + + /* Yield to other threads (timer, application threads) instead of + blocking indefinitely. This spin-poll eliminates the ~0.5 ms + kernel-wake latency of WaitForSingleObject(INFINITE) and reduces + context-switch overhead by >10x at the cost of higher CPU usage + during test runs. Drain any pending wake event to keep it clean. */ + WaitForSingleObject(_tx_win32_scheduler_wake_event, 0); + SwitchToThread(); +#endif } } @@ -185,12 +205,16 @@ DWORD wait_status; /* Exit Win32 critical section. */ _tx_win32_critical_section_release(&_tx_win32_critical_section); - /* Now suspend the main thread so the application thread can run. */ - WaitForSingleObject(_tx_win32_scheduler_semaphore, INFINITE); + /* Spin-poll for the application thread to return control. Using + SwitchToThread() between polls yields the CPU without blocking, + matching the low-latency approach used in the idle loop above. */ + while (WaitForSingleObject(_tx_win32_scheduler_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); } } + static VOID _tx_win32_semaphore_reset(HANDLE semaphore_handle) { @@ -216,17 +240,9 @@ void _tx_win32_critical_section_obtain(TX_WIN32_CRITICAL_SECTION *critical_se { /* Get the Win32 critical section. */ - if (WaitForSingleObject(critical_section -> tx_win32_critical_section_mutex_handle, INFINITE) != WAIT_OBJECT_0) - { - - /* Increment the system error counter and stop when the mutex cannot be acquired. */ - _tx_win32_system_error++; - while(1) - { - } - } + EnterCriticalSection(&critical_section -> tx_win32_critical_section_lock); - /* At this point we have the mutex. */ + /* At this point we have the critical section. */ /* Increment the nesting counter. */ critical_section -> tx_win32_critical_section_nested_count = 1; @@ -241,7 +257,7 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s { - /* Ensure the caller is the mutex owner. */ + /* Ensure the caller is the critical section owner. */ if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId()) { @@ -259,21 +275,8 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s /* Yes, it is being released clear the owner. */ critical_section -> tx_win32_critical_section_owner = 0; - /* Finally, release the mutex. */ - if (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) != TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } - - /* Just in case, make sure there the mutex is not owned. */ - while (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) == TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } + /* Finally, release the critical section. */ + LeaveCriticalSection(&critical_section -> tx_win32_critical_section_lock); } } } @@ -289,7 +292,7 @@ void _tx_win32_critical_section_release(TX_WIN32_CRITICAL_SECTION *critical_s void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *critical_section) { - /* Ensure the caller is the mutex owner. */ + /* Ensure the caller is the critical section owner. */ if (critical_section -> tx_win32_critical_section_owner == GetCurrentThreadId()) { @@ -303,21 +306,8 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri /* Yes, it is being release clear the owner. */ critical_section -> tx_win32_critical_section_owner = 0; - /* Finally, release the mutex. */ - if (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) != TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } - - /* Just in case, make sure there the mutex is not owned. */ - while (ReleaseMutex(critical_section -> tx_win32_critical_section_mutex_handle) == TX_TRUE) - { - - /* Increment the system error counter. */ - _tx_win32_system_error++; - } + /* Finally, release the critical section. */ + LeaveCriticalSection(&critical_section -> tx_win32_critical_section_lock); } } else @@ -327,5 +317,3 @@ void _tx_win32_critical_section_release_all(TX_WIN32_CRITICAL_SECTION *cri _tx_win32_system_error++; } } - - diff --git a/ports/win64/vs_2022/src/tx_thread_stack_build.c b/ports/win64/vs_2022/src/tx_thread_stack_build.c index d48a8f06..4f6be75c 100644 --- a/ports/win64/vs_2022/src/tx_thread_stack_build.c +++ b/ports/win64/vs_2022/src/tx_thread_stack_build.c @@ -16,6 +16,7 @@ // Some portions generated by Copilot (Opus 5). // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -186,9 +187,10 @@ DWORD threadid; handoff point and is ready to be scheduled. */ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); - /* Now suspend the thread initially. If the thread has already - been scheduled, this will return immediately. */ - WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, INFINITE); + /* Spin-poll for the scheduler to release this thread to run. + Matches the spin-poll pattern used in _tx_thread_system_return. */ + while (WaitForSingleObject(thread_ptr -> tx_thread_win32_thread_run_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); /* Acknowledge that the host thread is now able to execute ThreadX code. */ ReleaseSemaphore(thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); @@ -216,4 +218,3 @@ DWORD threadid; return EXIT_SUCCESS; } - diff --git a/ports/win64/vs_2022/src/tx_thread_system_return.c b/ports/win64/vs_2022/src/tx_thread_system_return.c index 7a8fc89c..9b1fa210 100644 --- a/ports/win64/vs_2022/src/tx_thread_system_return.c +++ b/ports/win64/vs_2022/src/tx_thread_system_return.c @@ -14,6 +14,7 @@ **************************************************************************/ // Some portions generated by Codex (gpt 5.4). +// Some portions generated by Codex (gpt-5.6-sol). /**************************************************************************/ /**************************************************************************/ @@ -167,9 +168,11 @@ DWORD threadid; ExitThread(0); } - /* Wait on the run semaphore for this thread. This won't get set again - until the thread is scheduled. */ - WaitForSingleObject(temp_run_semaphore, INFINITE); + /* Spin-poll for the scheduler to grant this thread a new time-slice. + SwitchToThread() between polls keeps the CPU available to the scheduler + and timer without paying the full kernel-wake cost of INFINITE. */ + while (WaitForSingleObject(temp_run_semaphore, 0) != WAIT_OBJECT_0) + SwitchToThread(); /* Acknowledge that the thread is once again executing ThreadX code. */ ReleaseSemaphore(temp_thread_ptr -> tx_thread_win32_thread_start_semaphore, 1, NULL); @@ -213,4 +216,3 @@ DWORD threadid; } } - diff --git a/test/smp/regression/testcontrol.c b/test/smp/regression/testcontrol.c index 816d1508..caf06e0d 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -12,6 +12,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). // Some portions generated by Copilot (Opus 5). +// Some portions generated by Codex (gpt-5.6-sol). #define TX_THREAD_SMP_SOURCE_CODE @@ -28,11 +29,17 @@ #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 @@ -1377,9 +1384,10 @@ TX_THREAD *thread_ptr; that takes milliseconds, so bounding it cannot turn a slow pass into a failure. - The report is written with write() rather than 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. + 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 @@ -1395,6 +1403,26 @@ 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) @@ -1404,9 +1432,9 @@ static void test_teardown_stage(const char *stage) if (test_teardown_trace != TX_FALSE) { - (void) write(2, "[teardown] ", 11); - (void) write(2, stage, strlen(stage)); - (void) write(2, "\n", 1); + test_teardown_write("[teardown] ", 11u); + test_teardown_write(stage, strlen(stage)); + test_teardown_write("\n", 1u); } } @@ -1445,7 +1473,7 @@ TX_THREAD *thread_ptr; (UINT) _tx_thread_preempt_disable, (ULONG) _tx_thread_created_count); if (length > 0) - (void) write(2, buffer, (size_t) length); + test_teardown_write(buffer, (size_t) length); for (core = 0; core < ((UINT) TX_THREAD_SMP_MAX_CORES); core++) { @@ -1457,7 +1485,7 @@ TX_THREAD *thread_ptr; test_teardown_thread_name(_tx_thread_current_ptr[core]), test_teardown_thread_name(_tx_thread_execute_ptr[core])); if (length > 0) - (void) write(2, buffer, (size_t) length); + test_teardown_write(buffer, (size_t) length); } /* Walk the created list, stopping at the head, and after a fixed number of @@ -1476,10 +1504,10 @@ TX_THREAD *thread_ptr; thread_ptr -> tx_thread_inherit_priority, thread_ptr -> tx_thread_smp_core_mapped, (ULONG) thread_ptr -> tx_thread_smp_core_control, - thread_ptr -> tx_thread_linux_deferred_preempt, - thread_ptr -> tx_thread_linux_suspension_type); + TEST_TEARDOWN_DEFERRED_PREEMPT(thread_ptr), + TEST_TEARDOWN_SUSPENSION_TYPE(thread_ptr)); if (length > 0) - (void) write(2, buffer, (size_t) length); + test_teardown_write(buffer, (size_t) length); thread_ptr = thread_ptr -> tx_thread_created_next; if (thread_ptr == _tx_thread_created_ptr) @@ -1498,6 +1526,41 @@ static void test_teardown_abort(const char *reason) /* 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) { @@ -1542,6 +1605,7 @@ ULONG limit_ms; return input; } +#endif /* Start the watchdog, stood down. Called before any test runs, so that no @@ -1549,7 +1613,11 @@ ULONG limit_ms; static void test_teardown_watchdog_start(void) { +#ifdef _WIN32 +uintptr_t watchdog_id; +#else pthread_t watchdog_id; +#endif char *value; @@ -1563,15 +1631,27 @@ char *value; /* 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 } diff --git a/test/tx/cmake/thread_transition/CMakeLists.txt b/test/tx/cmake/thread_transition/CMakeLists.txt index f414ed67..a5659238 100644 --- a/test/tx/cmake/thread_transition/CMakeLists.txt +++ b/test/tx/cmake/thread_transition/CMakeLists.txt @@ -1,3 +1,5 @@ +# Some portions generated by Codex (gpt-5.6-sol). + cmake_minimum_required(VERSION 3.13 FATAL_ERROR) cmake_policy(SET CMP0057 NEW) @@ -56,11 +58,22 @@ set(transition_sources # opposite, because calling the public names and letting tx_api.h decide what they # bind to is exactly what the TX_DISABLE_ERROR_CHECKING configuration is here to # demonstrate. +if(MSVC) + set(transition_kernel_compile_options + "/FI${SOURCE_DIR}/threadx_thread_transition_host_test_port.h") + set(transition_undefine_options /UTX_ENABLE_EVENT_TRACE + /UTX_ENABLE_EVENT_LOG) +else() + set(transition_kernel_compile_options + "-include;${SOURCE_DIR}/threadx_thread_transition_host_test_port.h") + set(transition_undefine_options -UTX_ENABLE_EVENT_TRACE + -UTX_ENABLE_EVENT_LOG) +endif() + set_source_files_properties( ${transition_kernel_sources} DIRECTORY ${CMAKE_CURRENT_LIST_DIR} - PROPERTIES COMPILE_OPTIONS - "-include;${SOURCE_DIR}/threadx_thread_transition_host_test_port.h") + PROPERTIES COMPILE_OPTIONS "${transition_kernel_compile_options}") # Each entry is a test-name suffix, a colon, and the feature macros that define the # configuration, separated by "|". A semicolon cannot be used as that separator: it @@ -80,16 +93,20 @@ foreach(configuration ${transition_configurations}) string(REPLACE "|" ";" configuration_macros ${configuration_macro_text}) set(test_name threadx_thread_transition_${configuration_name}_test) + # Keep the internal target name short. CMake includes it in every object path, + # and the descriptive test name can otherwise reach the legacy MSVC MAX_PATH + # boundary in a normally nested Windows worktree. + set(test_target tx_tt_${configuration_name}) - add_executable(${test_name} ${transition_sources}) + add_executable(${test_target} ${transition_sources}) target_include_directories( - ${test_name} + ${test_target} PRIVATE ${SOURCE_DIR} ${REPO_ROOT}/common/inc ${REPO_ROOT}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc) - target_compile_definitions(${test_name} PRIVATE ${configuration_macros}) + target_compile_definitions(${test_target} PRIVATE ${configuration_macros}) # This directory is configured once per build configuration of the tree, so # these executables inherit whichever feature macros that configuration sets -- @@ -103,13 +120,12 @@ foreach(configuration ${transition_configurations}) # lost by turning tracing off here. Event logging is turned off for the same # reason; no configuration of the tree enables it. # - # The -U flags have to reach the test source as well as the kernel sources, + # The undefine options have to reach the test source as well as the kernel sources, # because TX_ENABLE_EVENT_TRACE is visible to tx_api.h and the two must agree on # what the headers declare. Target compile options land after the directory's - # -D flags on the command line, which is what makes the -U effective. - target_compile_options(${test_name} PRIVATE -UTX_ENABLE_EVENT_TRACE - -UTX_ENABLE_EVENT_LOG) + # definition flags on the command line, which is what makes them effective. + target_compile_options(${test_target} PRIVATE ${transition_undefine_options}) - add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name}) + add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_target}) endforeach() diff --git a/test/tx/regression/threadx_thread_basic_execution_test.c b/test/tx/regression/threadx_thread_basic_execution_test.c index c51a677c..8372fcd9 100644 --- a/test/tx/regression/threadx_thread_basic_execution_test.c +++ b/test/tx/regression/threadx_thread_basic_execution_test.c @@ -1,4 +1,6 @@ /***************************************************************************/ + +/* Some portions generated by Codex (gpt-5.6-sol). */ /* Copyright (c) 2024 Microsoft Corporation */ /* Copyright (c) 2026 Eclipse ThreadX contributors */ /* */ @@ -401,7 +403,7 @@ VOID (*temp_mutex_release)(TX_THREAD *thread_ptr); test_thread.tx_thread_timer.tx_timer_internal_list_head = TX_NULL; test_thread.tx_thread_suspending = TX_TRUE; test_thread.tx_thread_delayed_suspend = TX_TRUE; -#if defined(_WIN64) || defined(TX_TIMER_EXTENSION_PTR_DEFINED) +#if defined(_WIN32) || defined(TX_TIMER_EXTENSION_PTR_DEFINED) { TX_TIMER_INTERNAL timeout_timer; TX_TIMER_INTERNAL *saved_expired_timer_ptr; diff --git a/test/tx/regression/threadx_trace_entry_update_test.c b/test/tx/regression/threadx_trace_entry_update_test.c index d827497a..6c6d9614 100644 --- a/test/tx/regression/threadx_trace_entry_update_test.c +++ b/test/tx/regression/threadx_trace_entry_update_test.c @@ -12,6 +12,7 @@ * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ +// Some portions generated by Codex (gpt-5.6-sol). /* This test 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 @@ -73,7 +74,7 @@ static TX_SEMAPHORE semaphore_0; static UCHAR trace_buffer[16384]; -/* Four blocks of 20 bytes: 100 / (20 + sizeof(void *)) on a 32-bit build. */ +/* Enough storage for several 20-byte blocks on both 32-bit and 64-bit builds. */ static UCHAR block_pool_area[100]; static UCHAR byte_pool_area[512]; @@ -213,14 +214,22 @@ void *byte_ptr; UINT i; - /* Empty the block pool. The first of these takes the immediate-success path - through tx_block_allocate, which carries the first update block. */ + /* Empty the block pool. The first allocation takes the immediate-success + path through tx_block_allocate, which carries the first update block. + The exact capacity is port-dependent because each block has a pointer- + sized header, so allocate until the pool reports that it is empty. */ held_block = TX_NULL; - for (i = 0; i < 4; i++) + for (i = 0; i < 6u; i++) { status = tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT); + if (status == TX_NO_MEMORY) + { + + break; + } + if (status != TX_SUCCESS) { @@ -236,6 +245,12 @@ UINT i; } } + if ((status != TX_NO_MEMORY) || (held_block == TX_NULL)) + { + + error++; + } + /* 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 and resume it goes through carry the |
