diff options
Diffstat (limited to 'ports/win32')
| -rw-r--r-- | ports/win32/vs_2019/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | ports/win32/vs_2019/inc/tx_port.h | 93 | ||||
| -rw-r--r-- | ports/win32/vs_2019/src/tx_initialize_low_level.c | 246 | ||||
| -rw-r--r-- | ports/win32/vs_2019/src/tx_thread_context_restore.c | 55 | ||||
| -rw-r--r-- | ports/win32/vs_2019/src/tx_thread_context_save.c | 19 | ||||
| -rw-r--r-- | ports/win32/vs_2019/src/tx_thread_interrupt_control.c | 15 | ||||
| -rw-r--r-- | ports/win32/vs_2019/src/tx_thread_schedule.c | 139 | ||||
| -rw-r--r-- | ports/win32/vs_2019/src/tx_thread_stack_build.c | 89 | ||||
| -rw-r--r-- | ports/win32/vs_2019/src/tx_thread_system_return.c | 27 |
9 files changed, 545 insertions, 139 deletions
diff --git a/ports/win32/vs_2019/CMakeLists.txt b/ports/win32/vs_2019/CMakeLists.txt index 5a766f06..d2210a8f 100644 --- a/ports/win32/vs_2019/CMakeLists.txt +++ b/ports/win32/vs_2019/CMakeLists.txt @@ -15,6 +15,7 @@ target_sources(${PROJECT_NAME} ) target_include_directories(${PROJECT_NAME} + SYSTEM PUBLIC ${CMAKE_CURRENT_LIST_DIR}/inc ) diff --git a/ports/win32/vs_2019/inc/tx_port.h b/ports/win32/vs_2019/inc/tx_port.h index 5dd65653..f878bd0e 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 **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -63,6 +65,7 @@ /* Define compiler library include files. */ +#include <stdint.h> #include <stdlib.h> #include <string.h> @@ -155,47 +158,12 @@ extern TEST_FLAG test_forced_mutex_timeout; #endif - -/* Define performance metric symbols. */ - -#ifndef TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO -#define TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO -#endif - -#ifndef TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO -#define TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO -#endif - -#ifndef TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO -#define TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO -#endif - -#ifndef TX_MUTEX_ENABLE_PERFORMANCE_INFO -#define TX_MUTEX_ENABLE_PERFORMANCE_INFO -#endif - -#ifndef TX_QUEUE_ENABLE_PERFORMANCE_INFO -#define TX_QUEUE_ENABLE_PERFORMANCE_INFO -#endif - -#ifndef TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO -#define TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO -#endif - -#ifndef TX_THREAD_ENABLE_PERFORMANCE_INFO -#define TX_THREAD_ENABLE_PERFORMANCE_INFO -#endif - -#ifndef TX_TIMER_ENABLE_PERFORMANCE_INFO -#define TX_TIMER_ENABLE_PERFORMANCE_INFO -#endif - -/* Enable trace info. */ - -#ifndef TX_ENABLE_EVENT_TRACE -#define TX_ENABLE_EVENT_TRACE -#endif - +/* Performance metrics and event trace are left to the application. Set the + TX_*_ENABLE_PERFORMANCE_INFO symbols, or TX_ENABLE_EVENT_TRACE, in + tx_user.h or on the command line when they are wanted. They extend the + control blocks, so a port that turns them on behind the application's + back changes structures the application also sees. This port and the + win64 one were the only two that did. */ /* Define ThreadX basic types for this port. */ @@ -338,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 @@ -396,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; @@ -500,7 +483,7 @@ VOID _tx_thread_interrupt_restore(UINT previous_posture); #ifdef TX_THREAD_INIT CHAR _tx_version_id[] = - "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX Win32/Visual Studio Version 6.5.1.202602a *"; + "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX Win32/Visual Studio Version 6.5.2.202603 *"; #else extern CHAR _tx_version_id[]; #endif @@ -510,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..82bee742 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 @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Codex (gpt 5.4). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ @@ -37,19 +37,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 +172,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 +211,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 +251,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 +346,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..215489c9 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 **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -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..769c650b 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 **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -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..03a017fb 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 **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -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..e9dc43d7 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 **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -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 da51e7c4..7b6e139b 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,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -75,6 +77,7 @@ DWORD WINAPI _tx_win32_thread_entry(LPVOID p); /**************************************************************************/ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID)) { +ALIGN_TYPE fake_stack_ptr; /* Create a Win32 thread for the application thread. */ thread_ptr -> tx_thread_win32_thread_handle = @@ -111,6 +114,20 @@ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID)) } } + /* 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; @@ -119,8 +136,13 @@ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID)) tx_interrupt_control nesting. */ thread_ptr -> tx_thread_win32_int_disabled_flag = 0; - /* Setup a fake thread stack pointer. */ - thread_ptr -> tx_thread_stack_ptr = (VOID *) (((CHAR *) thread_ptr -> tx_thread_stack_end) - 8); + /* Setup a fake thread stack pointer. The stack end points at the last byte of the + thread's stack area and is therefore not necessarily aligned, so round the result + down to a ULONG boundary. This pointer is dereferenced as a ULONG below and is + also the starting value of the stack checking logic's highest used pointer. */ + fake_stack_ptr = (ALIGN_TYPE) ((VOID *) (((CHAR *) thread_ptr -> tx_thread_stack_end) - 8)); + fake_stack_ptr = fake_stack_ptr & (~((ALIGN_TYPE) (sizeof(ULONG) - 1))); + thread_ptr -> tx_thread_stack_ptr = (VOID *) ((ALIGN_TYPE) fake_stack_ptr); /* Clear the first word of the stack. */ *(((ULONG *) thread_ptr -> tx_thread_stack_ptr) - 1) = 0; @@ -128,24 +150,77 @@ VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID)) /* 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..a14c326e 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 **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -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); } } - |
