diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 655a2f697..4d3e3b509 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 #include #include @@ -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 c362b3e73..e531b1ca9 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 36fa1e4b0..ae559bed8 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 2f4afb403..4574b8277 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 019aabefd..ca73b7fba 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 d67efee85..1fa11c596 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 ccbabefd1..f473ec953 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 601761357..52f65b4f8 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 eeabf651c..2b486ed95 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 b7e965758..b41477d75 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 9786e7cbe..f860d61f0 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 ab50a3c9e..b38a0749b 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 d08305ee8..f6bef9495 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 d48a8f061..4f6be75cf 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 7a8fc89c0..9b1fa210a 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 59d8e69a7..025f76030 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -11,6 +11,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 Codex (gpt-5.6-sol). #define TX_THREAD_SMP_SOURCE_CODE @@ -27,11 +28,17 @@ #include "tx_event_flags.h" #include #include +#ifdef _WIN32 +#include +#include +#include +#else #include #include #include #include #include +#endif #define TEST_STACK_SIZE 6144 @@ -1368,9 +1375,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 @@ -1386,6 +1394,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) @@ -1395,9 +1423,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); } } @@ -1436,7 +1464,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++) { @@ -1448,7 +1476,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 @@ -1467,10 +1495,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) @@ -1489,6 +1517,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) { @@ -1533,6 +1596,7 @@ ULONG limit_ms; return input; } +#endif /* Start the watchdog, stood down. Called before any test runs, so that no @@ -1540,7 +1604,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; @@ -1554,15 +1622,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 f414ed675..a56592382 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 c51a677c3..8372fcd9d 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 d827497af..6c6d96143 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