From 09c8547a3f37c50e14639447b0ddff2c44c02380 Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Wed, 14 Oct 2026 01:58:24 +0800 Subject: [PATCH] Passed a mutex waiter's priority change on to the mutex owner A mutex owner inherits a waiter's priority only at the moment the waiter blocks in tx_mutex_get. _tx_thread_priority_change on a thread that is not ready updated that thread's own priority fields and nothing else, so a thread waiting on a TX_INHERIT mutex could be raised above the owner without the owner following, and a waiter the owner had inherited from could be lowered while the owner kept the old priority. When the thread is suspended on a mutex, _tx_thread_priority_change now calls a new internal function, _tx_mutex_inherit_priority_update, which recomputes the mutex's highest waiting priority, the owner's inherited priority across every mutex it holds, and the owner's priority. The call goes through a new function pointer, _tx_thread_mutex_priority_update, set by tx_mutex_create in the same way as _tx_thread_mutex_release, so the thread services still do not link the mutex services. Applied to common and common_smp. threadx_mutex_inherit_waiter_priority_change_test changes the priorities of two waiters in four steps that raise the owner, lower it to the other waiter, and finally return it to its own priority. Added to both suites. Without the fix it fails with ERROR #30 in both; with it, the mutex tests pass in both suites' default_build_coverage configuration, and the changed sources build cleanly under -Wall -Wextra -Werror with TX_NOT_INTERRUPTABLE, TX_MISRA_ENABLE, TX_DISABLE_PREEMPTION_THRESHOLD and event trace. --- common/inc/tx_mutex.h | 1 + common/inc/tx_thread.h | 7 + common/src/tx_mutex_cleanup.c | 179 ++++++++++++++++++ common/src/tx_mutex_create.c | 4 +- common/src/tx_thread_initialize.c | 10 + common/src/tx_thread_priority_change.c | 46 ++++- common_smp/inc/tx_mutex.h | 1 + common_smp/inc/tx_thread.h | 7 + common_smp/src/tx_mutex_cleanup.c | 179 ++++++++++++++++++ common_smp/src/tx_mutex_create.c | 4 +- common_smp/src/tx_thread_initialize.c | 10 + common_smp/src/tx_thread_priority_change.c | 46 ++++- test/smp/cmake/regression/CMakeLists.txt | 1 + test/smp/regression/testcontrol.c | 2 + ...utex_inherit_waiter_priority_change_test.c | 153 +++++++++++++++ test/tx/cmake/regression/CMakeLists.txt | 1 + test/tx/regression/testcontrol.c | 2 + ...utex_inherit_waiter_priority_change_test.c | 153 +++++++++++++++ 18 files changed, 800 insertions(+), 6 deletions(-) create mode 100644 test/smp/regression/threadx_mutex_inherit_waiter_priority_change_test.c create mode 100644 test/tx/regression/threadx_mutex_inherit_waiter_priority_change_test.c diff --git a/common/inc/tx_mutex.h b/common/inc/tx_mutex.h index b0b46c2f9..f062cc4a5 100644 --- a/common/inc/tx_mutex.h +++ b/common/inc/tx_mutex.h @@ -84,6 +84,7 @@ VOID _tx_mutex_initialize(VOID); VOID _tx_mutex_cleanup(TX_THREAD *thread_ptr, ULONG suspension_sequence); VOID _tx_mutex_thread_release(TX_THREAD *thread_ptr); VOID _tx_mutex_priority_change(TX_THREAD *thread_ptr, UINT new_priority); +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr); /* Mutex management component data declarations follow. */ diff --git a/common/inc/tx_thread.h b/common/inc/tx_thread.h index 5ba8b3f54..cbfecbbc7 100644 --- a/common/inc/tx_thread.h +++ b/common/inc/tx_thread.h @@ -390,6 +390,13 @@ THREAD_DECLARE volatile UINT _tx_thread_preempt_disable; THREAD_DECLARE VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Define the global function pointer that updates the priority inheritance of a mutex + after the priority of a thread waiting on it changes. This pointer is setup during + mutex initialization. */ + +THREAD_DECLARE VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); + + /* Define the global build options variable. This contains a bit map representing how the ThreadX library was built. The following are the bit field definitions: diff --git a/common/src/tx_mutex_cleanup.c b/common/src/tx_mutex_cleanup.c index d32f69fc0..3adb21ada 100644 --- a/common/src/tx_mutex_cleanup.c +++ b/common/src/tx_mutex_cleanup.c @@ -307,3 +307,182 @@ UINT status; TX_RESTORE } + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_mutex_inherit_priority_update PORTABLE C */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function brings the priority inheritance of a mutex and of */ +/* its owner up to date after the set of threads waiting on the mutex, */ +/* or the priority of one of them, has changed other than through */ +/* tx_mutex_get or tx_mutex_put. The highest priority waiting on the */ +/* mutex is recomputed from its suspension list, the owner's inherited */ +/* priority is recomputed from every priority inheritance mutex it */ +/* owns, and the owner's priority is changed if it differs from the */ +/* higher of that and its own user priority. */ +/* */ +/* Unless TX_NOT_INTERRUPTABLE is defined, the caller must have */ +/* preemption disabled and interrupts enabled, as for */ +/* _tx_mutex_priority_change. Otherwise interrupts must be disabled. */ +/* */ +/* INPUT */ +/* */ +/* mutex_ptr Pointer to mutex control block */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_mutex_prioritize Move highest priority waiter to */ +/* the front of the list */ +/* _tx_mutex_priority_change Change owner's priority */ +/* */ +/* CALLED BY */ +/* */ +/* ThreadX internal code */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr) +{ + +#ifndef TX_NOT_INTERRUPTABLE +TX_INTERRUPT_SAVE_AREA +#endif + +TX_THREAD *owner_ptr; +TX_THREAD *suspended_thread; +TX_MUTEX *next_mutex; +UINT inheritance_priority; +UINT new_priority; +#ifdef TX_MISRA_ENABLE +UINT status; +#endif + + + /* Determine if there is more than one thread suspended on the mutex. */ + if (mutex_ptr -> tx_mutex_suspended_count > ((UINT) 1)) + { + + /* Yes, prioritize the list so the highest priority thread is placed at the + front of the suspension list. */ +#ifdef TX_MISRA_ENABLE + do + { + status = _tx_mutex_prioritize(mutex_ptr); + } while (status != TX_SUCCESS); +#else + _tx_mutex_prioritize(mutex_ptr); +#endif + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Pickup the thread at the front of the suspension list. */ + suspended_thread = mutex_ptr -> tx_mutex_suspension_list; + + /* Determine if there still are threads suspended for this mutex. */ + if (suspended_thread != TX_NULL) + { + + /* Setup the highest priority thread waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = suspended_thread -> tx_thread_priority; + } + else + { + + /* No thread is waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = ((UINT) TX_MAX_PRIORITIES); + } + + /* Pickup the mutex owner. */ + owner_ptr = mutex_ptr -> tx_mutex_owner; + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* The owner is not NULL. A thread could only suspend on this mutex while a + thread owned it, since tx_mutex_get raises the owner's priority when it does, + and the owner keeps the mutex for as long as this processing can run. */ + + /* Default the inheritance priority to disabled. */ + inheritance_priority = ((UINT) TX_MAX_PRIORITIES); + + /* Search the mutexes owned by this thread to determine the highest priority + it still inherits. */ + next_mutex = owner_ptr -> tx_thread_owned_mutex_list; + while (next_mutex != TX_NULL) + { + + /* Does this mutex support priority inheritance? */ + if (next_mutex -> tx_mutex_inherit == TX_TRUE) + { + + /* Determine if highest priority field of the mutex is higher than the + inheritance priority so far. */ + if (next_mutex -> tx_mutex_highest_priority_waiting < inheritance_priority) + { + + /* Use this priority. */ + inheritance_priority = next_mutex -> tx_mutex_highest_priority_waiting; + } + } + + /* Move mutex pointer to the next mutex in the list. */ + next_mutex = next_mutex -> tx_mutex_owned_next; + + /* Are we at the end of the list? */ + if (next_mutex == owner_ptr -> tx_thread_owned_mutex_list) + { + + /* Yes, set the next mutex to NULL. */ + next_mutex = TX_NULL; + } + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Set the inherit priority to that of the highest priority thread waiting on + any mutex the owner holds. */ + owner_ptr -> tx_thread_inherit_priority = inheritance_priority; + + /* The owner runs at the higher of its own priority and the inherited one. */ + new_priority = owner_ptr -> tx_thread_user_priority; + if (inheritance_priority < new_priority) + { + + /* Use the inherited priority. */ + new_priority = inheritance_priority; + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Determine if the owner's priority has to change. */ + if (owner_ptr -> tx_thread_priority != new_priority) + { + + /* Yes, change the priority of the owner. */ + _tx_mutex_priority_change(owner_ptr, new_priority); + } +} diff --git a/common/src/tx_mutex_create.c b/common/src/tx_mutex_create.c index a1f5eba85..ed559f302 100644 --- a/common/src/tx_mutex_create.c +++ b/common/src/tx_mutex_create.c @@ -11,7 +11,6 @@ /* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ /** */ @@ -92,6 +91,9 @@ TX_MUTEX *previous_mutex; /* Setup the thread mutex release function pointer. */ _tx_thread_mutex_release = &(_tx_mutex_thread_release); + /* Setup the thread mutex priority update function pointer. */ + _tx_thread_mutex_priority_update = &(_tx_mutex_inherit_priority_update); + /* Place the mutex on the list of created mutexes. First, check for an empty list. */ if (_tx_mutex_created_count == TX_EMPTY) diff --git a/common/src/tx_thread_initialize.c b/common/src/tx_thread_initialize.c index c9cfea339..97b1072a7 100644 --- a/common/src/tx_thread_initialize.c +++ b/common/src/tx_thread_initialize.c @@ -139,6 +139,13 @@ volatile UINT _tx_thread_preempt_disable; VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Define the global function pointer that updates the priority inheritance of a mutex + after the priority of a thread waiting on it changes. This pointer is setup during + mutex initialization. */ + +VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); + + /* Define the global build options variable. This contains a bit map representing how the ThreadX library was built. The following are the bit field definitions: @@ -348,6 +355,9 @@ VOID _tx_thread_initialize(VOID) /* Initialize the thread mutex release function pointer. */ _tx_thread_mutex_release = TX_NULL; + /* Initialize the thread mutex priority update function pointer. */ + _tx_thread_mutex_priority_update = TX_NULL; + #ifdef TX_ENABLE_STACK_CHECKING /* Clear application registered stack error handler. */ diff --git a/common/src/tx_thread_priority_change.c b/common/src/tx_thread_priority_change.c index bdf2a9930..8479526d1 100644 --- a/common/src/tx_thread_priority_change.c +++ b/common/src/tx_thread_priority_change.c @@ -66,6 +66,7 @@ /* _tx_thread_system_ni_resume Non-interruptable resume thread */ /* _tx_thread_system_suspend Suspend thread */ /* _tx_thread_system_ni_suspend Non-interruptable suspend thread */ +/* _tx_thread_mutex_priority_update Update mutex inheritance */ /* _tx_thread_system_preempt_check Check for preemption */ /* */ /* CALLED BY */ @@ -79,6 +80,7 @@ UINT _tx_thread_priority_change(TX_THREAD *thread_ptr, UINT new_priority, UINT TX_INTERRUPT_SAVE_AREA TX_THREAD *execute_ptr; +TX_MUTEX *mutex_ptr; TX_THREAD *next_execute_ptr; UINT original_priority; @@ -121,8 +123,48 @@ UINT original_priority; thread_ptr -> tx_thread_preempt_threshold = thread_ptr -> tx_thread_inherit_priority; } - /* Restore interrupts. */ - TX_RESTORE + /* Determine if the thread is waiting on a mutex. */ + if ((thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) && (thread_ptr -> tx_thread_suspend_cleanup != TX_NULL)) + { + + /* Yes, the mutex owner may have inherited the old priority of this thread, or + may now have to inherit the new one. The update function pointer is set, + since a mutex has been created. */ + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + +#ifndef TX_NOT_INTERRUPTABLE + + /* Temporarily disable preemption. */ + _tx_thread_preempt_disable++; + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Update the priority inheritance of the mutex owner. */ + (_tx_thread_mutex_priority_update)(mutex_ptr); + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE + + /* Back off the preemption disable. */ + _tx_thread_preempt_disable--; +#endif + + /* Restore interrupts. */ + TX_RESTORE + + /* Check for preemption. */ + _tx_thread_system_preempt_check(); + } + else + { + + /* Restore interrupts. */ + TX_RESTORE + } } else { diff --git a/common_smp/inc/tx_mutex.h b/common_smp/inc/tx_mutex.h index b0b46c2f9..f062cc4a5 100644 --- a/common_smp/inc/tx_mutex.h +++ b/common_smp/inc/tx_mutex.h @@ -84,6 +84,7 @@ VOID _tx_mutex_initialize(VOID); VOID _tx_mutex_cleanup(TX_THREAD *thread_ptr, ULONG suspension_sequence); VOID _tx_mutex_thread_release(TX_THREAD *thread_ptr); VOID _tx_mutex_priority_change(TX_THREAD *thread_ptr, UINT new_priority); +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr); /* Mutex management component data declarations follow. */ diff --git a/common_smp/inc/tx_thread.h b/common_smp/inc/tx_thread.h index 1ffe88287..f8ce03c7d 100644 --- a/common_smp/inc/tx_thread.h +++ b/common_smp/inc/tx_thread.h @@ -315,6 +315,13 @@ THREAD_DECLARE volatile UINT _tx_thread_preempt_disable; THREAD_DECLARE VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Define the global function pointer that updates the priority inheritance of a mutex + after the priority of a thread waiting on it changes. This pointer is setup during + mutex initialization. */ + +THREAD_DECLARE VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); + + /* Define the global build options variable. This contains a bit map representing how the ThreadX library was built. The following are the bit field definitions: diff --git a/common_smp/src/tx_mutex_cleanup.c b/common_smp/src/tx_mutex_cleanup.c index d32f69fc0..3adb21ada 100644 --- a/common_smp/src/tx_mutex_cleanup.c +++ b/common_smp/src/tx_mutex_cleanup.c @@ -307,3 +307,182 @@ UINT status; TX_RESTORE } + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_mutex_inherit_priority_update PORTABLE C */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function brings the priority inheritance of a mutex and of */ +/* its owner up to date after the set of threads waiting on the mutex, */ +/* or the priority of one of them, has changed other than through */ +/* tx_mutex_get or tx_mutex_put. The highest priority waiting on the */ +/* mutex is recomputed from its suspension list, the owner's inherited */ +/* priority is recomputed from every priority inheritance mutex it */ +/* owns, and the owner's priority is changed if it differs from the */ +/* higher of that and its own user priority. */ +/* */ +/* Unless TX_NOT_INTERRUPTABLE is defined, the caller must have */ +/* preemption disabled and interrupts enabled, as for */ +/* _tx_mutex_priority_change. Otherwise interrupts must be disabled. */ +/* */ +/* INPUT */ +/* */ +/* mutex_ptr Pointer to mutex control block */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_mutex_prioritize Move highest priority waiter to */ +/* the front of the list */ +/* _tx_mutex_priority_change Change owner's priority */ +/* */ +/* CALLED BY */ +/* */ +/* ThreadX internal code */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr) +{ + +#ifndef TX_NOT_INTERRUPTABLE +TX_INTERRUPT_SAVE_AREA +#endif + +TX_THREAD *owner_ptr; +TX_THREAD *suspended_thread; +TX_MUTEX *next_mutex; +UINT inheritance_priority; +UINT new_priority; +#ifdef TX_MISRA_ENABLE +UINT status; +#endif + + + /* Determine if there is more than one thread suspended on the mutex. */ + if (mutex_ptr -> tx_mutex_suspended_count > ((UINT) 1)) + { + + /* Yes, prioritize the list so the highest priority thread is placed at the + front of the suspension list. */ +#ifdef TX_MISRA_ENABLE + do + { + status = _tx_mutex_prioritize(mutex_ptr); + } while (status != TX_SUCCESS); +#else + _tx_mutex_prioritize(mutex_ptr); +#endif + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Pickup the thread at the front of the suspension list. */ + suspended_thread = mutex_ptr -> tx_mutex_suspension_list; + + /* Determine if there still are threads suspended for this mutex. */ + if (suspended_thread != TX_NULL) + { + + /* Setup the highest priority thread waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = suspended_thread -> tx_thread_priority; + } + else + { + + /* No thread is waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = ((UINT) TX_MAX_PRIORITIES); + } + + /* Pickup the mutex owner. */ + owner_ptr = mutex_ptr -> tx_mutex_owner; + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* The owner is not NULL. A thread could only suspend on this mutex while a + thread owned it, since tx_mutex_get raises the owner's priority when it does, + and the owner keeps the mutex for as long as this processing can run. */ + + /* Default the inheritance priority to disabled. */ + inheritance_priority = ((UINT) TX_MAX_PRIORITIES); + + /* Search the mutexes owned by this thread to determine the highest priority + it still inherits. */ + next_mutex = owner_ptr -> tx_thread_owned_mutex_list; + while (next_mutex != TX_NULL) + { + + /* Does this mutex support priority inheritance? */ + if (next_mutex -> tx_mutex_inherit == TX_TRUE) + { + + /* Determine if highest priority field of the mutex is higher than the + inheritance priority so far. */ + if (next_mutex -> tx_mutex_highest_priority_waiting < inheritance_priority) + { + + /* Use this priority. */ + inheritance_priority = next_mutex -> tx_mutex_highest_priority_waiting; + } + } + + /* Move mutex pointer to the next mutex in the list. */ + next_mutex = next_mutex -> tx_mutex_owned_next; + + /* Are we at the end of the list? */ + if (next_mutex == owner_ptr -> tx_thread_owned_mutex_list) + { + + /* Yes, set the next mutex to NULL. */ + next_mutex = TX_NULL; + } + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Set the inherit priority to that of the highest priority thread waiting on + any mutex the owner holds. */ + owner_ptr -> tx_thread_inherit_priority = inheritance_priority; + + /* The owner runs at the higher of its own priority and the inherited one. */ + new_priority = owner_ptr -> tx_thread_user_priority; + if (inheritance_priority < new_priority) + { + + /* Use the inherited priority. */ + new_priority = inheritance_priority; + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Determine if the owner's priority has to change. */ + if (owner_ptr -> tx_thread_priority != new_priority) + { + + /* Yes, change the priority of the owner. */ + _tx_mutex_priority_change(owner_ptr, new_priority); + } +} diff --git a/common_smp/src/tx_mutex_create.c b/common_smp/src/tx_mutex_create.c index a1f5eba85..ed559f302 100644 --- a/common_smp/src/tx_mutex_create.c +++ b/common_smp/src/tx_mutex_create.c @@ -11,7 +11,6 @@ /* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ /** */ @@ -92,6 +91,9 @@ TX_MUTEX *previous_mutex; /* Setup the thread mutex release function pointer. */ _tx_thread_mutex_release = &(_tx_mutex_thread_release); + /* Setup the thread mutex priority update function pointer. */ + _tx_thread_mutex_priority_update = &(_tx_mutex_inherit_priority_update); + /* Place the mutex on the list of created mutexes. First, check for an empty list. */ if (_tx_mutex_created_count == TX_EMPTY) diff --git a/common_smp/src/tx_thread_initialize.c b/common_smp/src/tx_thread_initialize.c index f37931096..11a1003db 100644 --- a/common_smp/src/tx_thread_initialize.c +++ b/common_smp/src/tx_thread_initialize.c @@ -178,6 +178,13 @@ volatile UINT _tx_thread_preempt_disable; VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Define the global function pointer that updates the priority inheritance of a mutex + after the priority of a thread waiting on it changes. This pointer is setup during + mutex initialization. */ + +VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); + + /* Define the global build options variable. This contains a bit map representing how the ThreadX library was built. The following are the bit field definitions: @@ -386,6 +393,9 @@ VOID _tx_thread_initialize(VOID) /* Initialize the thread mutex release function pointer. */ _tx_thread_mutex_release = TX_NULL; + /* Initialize the thread mutex priority update function pointer. */ + _tx_thread_mutex_priority_update = TX_NULL; + #ifdef TX_ENABLE_STACK_CHECKING /* Clear application registered stack error handler. */ diff --git a/common_smp/src/tx_thread_priority_change.c b/common_smp/src/tx_thread_priority_change.c index 57d6eda79..a13d6477d 100644 --- a/common_smp/src/tx_thread_priority_change.c +++ b/common_smp/src/tx_thread_priority_change.c @@ -69,6 +69,7 @@ /* _tx_thread_system_ni_resume Non-interruptable resume */ /* _tx_thread_system_suspend Suspend thread */ /* _tx_thread_system_ni_suspend Non-interruptable suspend */ +/* _tx_thread_mutex_priority_update Update mutex inheritance */ /* _tx_thread_system_preempt_check Check for preemption */ /* */ /* CALLED BY */ @@ -82,6 +83,7 @@ UINT _tx_thread_priority_change(TX_THREAD *thread_ptr, UINT new_priority, UINT TX_INTERRUPT_SAVE_AREA TX_THREAD *execute_ptr; +TX_MUTEX *mutex_ptr; UINT core_index; UINT original_priority; UINT lowest_priority; @@ -175,8 +177,48 @@ UINT status; thread_ptr -> tx_thread_preempt_threshold = thread_ptr -> tx_thread_inherit_priority; } - /* Restore interrupts. */ - TX_RESTORE + /* Determine if the thread is waiting on a mutex. */ + if ((thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) && (thread_ptr -> tx_thread_suspend_cleanup != TX_NULL)) + { + + /* Yes, the mutex owner may have inherited the old priority of this thread, or + may now have to inherit the new one. The update function pointer is set, + since a mutex has been created. */ + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + +#ifndef TX_NOT_INTERRUPTABLE + + /* Temporarily disable preemption. */ + _tx_thread_preempt_disable++; + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Update the priority inheritance of the mutex owner. */ + (_tx_thread_mutex_priority_update)(mutex_ptr); + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE + + /* Back off the preemption disable. */ + _tx_thread_preempt_disable--; +#endif + + /* Restore interrupts. */ + TX_RESTORE + + /* Check for preemption. */ + _tx_thread_system_preempt_check(); + } + else + { + + /* Restore interrupts. */ + TX_RESTORE + } /* Done, return success. */ status = TX_SUCCESS; diff --git a/test/smp/cmake/regression/CMakeLists.txt b/test/smp/cmake/regression/CMakeLists.txt index cdf0c5859..2568b4516 100644 --- a/test/smp/cmake/regression/CMakeLists.txt +++ b/test/smp/cmake/regression/CMakeLists.txt @@ -47,6 +47,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_mutex_delete_test.c ${SOURCE_DIR}/threadx_mutex_information_test.c ${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c + ${SOURCE_DIR}/threadx_mutex_inherit_waiter_priority_change_test.c ${SOURCE_DIR}/threadx_mutex_no_preemption_test.c ${SOURCE_DIR}/threadx_mutex_preemption_test.c ${SOURCE_DIR}/threadx_mutex_priority_inheritance_test.c diff --git a/test/smp/regression/testcontrol.c b/test/smp/regression/testcontrol.c index c964dc03a..7781845d8 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -201,6 +201,7 @@ void threadx_mutex_preemption_application_define(void *); void threadx_mutext_no_preemption_application_define(void *); void threadx_mutex_suspension_timeout_application_define(void *); void threadx_mutex_thread_terminate_application_define(void *); +void threadx_mutex_inherit_waiter_priority_change_application_define(void *); void threadx_mutex_priority_inheritance_application_define(void *); void threadx_mutex_nested_priority_inheritance_application_define(void *); void threadx_mutex_prioritize_application_define(void *); @@ -348,6 +349,7 @@ TEST_ENTRY test_control_tests[] = threadx_mutext_no_preemption_application_define, threadx_mutex_suspension_timeout_application_define, threadx_mutex_thread_terminate_application_define, + threadx_mutex_inherit_waiter_priority_change_application_define, threadx_mutex_priority_inheritance_application_define, threadx_mutex_prioritize_application_define, threadx_mutex_nested_priority_inheritance_application_define, diff --git a/test/smp/regression/threadx_mutex_inherit_waiter_priority_change_test.c b/test/smp/regression/threadx_mutex_inherit_waiter_priority_change_test.c new file mode 100644 index 000000000..2444d0db6 --- /dev/null +++ b/test/smp/regression/threadx_mutex_inherit_waiter_priority_change_test.c @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ + +/* This test checks that changing the priority of a thread waiting on a + priority-inheritance mutex is passed on to the mutex owner. Raising a waiter above + the owner must raise the owner, and lowering the waiter the owner inherited from + must lower the owner to the highest priority still waiting, or to its own. */ + +#include +#include "tx_api.h" + + +/* Wait a tick at a time, for at most 200 ticks, until a condition holds. A thread the + control thread starts can take more than a tick to get there on an SMP host. */ + +#define WAIT_UNTIL(condition) { UINT ticks = 0; while ((!(condition)) && (ticks < 200)) { tx_thread_sleep(1); ticks++; } } + +#define WAITERS 2 +#define STEPS 4 + + +static TX_THREAD control_thread; +static TX_THREAD owner_thread; +static TX_THREAD waiter[WAITERS]; +static CHAR *thread_stack[WAITERS + 1]; +static TX_MUTEX mutex_0; + + +/* Define thread prototypes. */ + +static void control_thread_entry(ULONG thread_input); + + +/* Prototype for test control return. */ + +void test_control_return(UINT status); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_mutex_inherit_waiter_priority_change_application_define(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; +UINT i; + + + /* Put first available memory address into a character pointer. */ + pointer = (CHAR *) first_unused_memory; + + status = tx_thread_create(&control_thread, "control thread", control_thread_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + /* The remaining threads are created by the control thread. */ + for (i = 0; i <= WAITERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Waiter Priority Change Test................... ERROR #1\n"); + test_control_return(1); + } +} + + +/* The owner takes the mutex and parks, and the waiters block on it. */ + +static void mutex_thread_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +static void check(UINT condition, UINT error) +{ + + if (condition == TX_FALSE) + { + + printf("ERROR #%u\n", error); + test_control_return(1); + } +} + + +static void start_thread(TX_THREAD *thread_ptr, UINT stack, UINT priority) +{ + + check(tx_thread_create(thread_ptr, "test thread", mutex_thread_entry, 0, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); +} + + +/* The owner at 15 holds the mutex, with waiter 0 at 20 and waiter 1 at 8, so the owner + runs at 8. Each step changes the priority of one waiter, and the owner must follow + the highest priority left waiting. */ + +static void control_thread_entry(ULONG thread_input) +{ + +static const UINT step_waiter[STEPS] = {0, 0, 1, 0}; +static const UINT step_priority[STEPS] = {5, 12, 20, 20}; +static const UINT step_highest_waiting[STEPS] = {5, 8, 12, 20}; +static const UINT step_owner_priority[STEPS] = {5, 8, 12, 15}; +UINT old_priority; +UINT i; + + + /* Inform user. */ + printf("Running Mutex Inherit Waiter Priority Change Test................... "); + + start_thread(&owner_thread, WAITERS, 15); + WAIT_UNTIL((mutex_0.tx_mutex_owner == &owner_thread) && (owner_thread.tx_thread_state == TX_SUSPENDED)) + start_thread(&waiter[0], 0, 20); + start_thread(&waiter[1], 1, 8); + WAIT_UNTIL(mutex_0.tx_mutex_suspended_count == WAITERS) + check(owner_thread.tx_thread_priority == 8, 10); + + for (i = 0; i < STEPS; i++) + { + tx_thread_priority_change(&waiter[step_waiter[i]], step_priority[i], &old_priority); + check(waiter[step_waiter[i]].tx_thread_state == TX_MUTEX_SUSP, 20 + i); + check(mutex_0.tx_mutex_highest_priority_waiting == step_highest_waiting[i], 30 + i); + check(owner_thread.tx_thread_priority == step_owner_priority[i], 40 + i); + } + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +} diff --git a/test/tx/cmake/regression/CMakeLists.txt b/test/tx/cmake/regression/CMakeLists.txt index 6b94a8576..4900c36f6 100644 --- a/test/tx/cmake/regression/CMakeLists.txt +++ b/test/tx/cmake/regression/CMakeLists.txt @@ -44,6 +44,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_mutex_delete_test.c ${SOURCE_DIR}/threadx_mutex_information_test.c ${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c + ${SOURCE_DIR}/threadx_mutex_inherit_waiter_priority_change_test.c ${SOURCE_DIR}/threadx_mutex_no_preemption_test.c ${SOURCE_DIR}/threadx_mutex_preemption_test.c ${SOURCE_DIR}/threadx_mutex_priority_inheritance_test.c diff --git a/test/tx/regression/testcontrol.c b/test/tx/regression/testcontrol.c index bec50ee76..e9e5daa5d 100644 --- a/test/tx/regression/testcontrol.c +++ b/test/tx/regression/testcontrol.c @@ -173,6 +173,7 @@ void threadx_mutex_preemption_application_define(void *); void threadx_mutext_no_preemption_application_define(void *); void threadx_mutex_suspension_timeout_application_define(void *); void threadx_mutex_thread_terminate_application_define(void *); +void threadx_mutex_inherit_waiter_priority_change_application_define(void *); void threadx_mutex_priority_inheritance_application_define(void *); void threadx_mutex_nested_priority_inheritance_application_define(void *); void threadx_mutex_prioritize_application_define(void *); @@ -292,6 +293,7 @@ TEST_ENTRY test_control_tests[] = threadx_mutext_no_preemption_application_define, threadx_mutex_suspension_timeout_application_define, threadx_mutex_thread_terminate_application_define, + threadx_mutex_inherit_waiter_priority_change_application_define, threadx_mutex_priority_inheritance_application_define, threadx_mutex_prioritize_application_define, threadx_mutex_nested_priority_inheritance_application_define, diff --git a/test/tx/regression/threadx_mutex_inherit_waiter_priority_change_test.c b/test/tx/regression/threadx_mutex_inherit_waiter_priority_change_test.c new file mode 100644 index 000000000..2444d0db6 --- /dev/null +++ b/test/tx/regression/threadx_mutex_inherit_waiter_priority_change_test.c @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ + +/* This test checks that changing the priority of a thread waiting on a + priority-inheritance mutex is passed on to the mutex owner. Raising a waiter above + the owner must raise the owner, and lowering the waiter the owner inherited from + must lower the owner to the highest priority still waiting, or to its own. */ + +#include +#include "tx_api.h" + + +/* Wait a tick at a time, for at most 200 ticks, until a condition holds. A thread the + control thread starts can take more than a tick to get there on an SMP host. */ + +#define WAIT_UNTIL(condition) { UINT ticks = 0; while ((!(condition)) && (ticks < 200)) { tx_thread_sleep(1); ticks++; } } + +#define WAITERS 2 +#define STEPS 4 + + +static TX_THREAD control_thread; +static TX_THREAD owner_thread; +static TX_THREAD waiter[WAITERS]; +static CHAR *thread_stack[WAITERS + 1]; +static TX_MUTEX mutex_0; + + +/* Define thread prototypes. */ + +static void control_thread_entry(ULONG thread_input); + + +/* Prototype for test control return. */ + +void test_control_return(UINT status); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_mutex_inherit_waiter_priority_change_application_define(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; +UINT i; + + + /* Put first available memory address into a character pointer. */ + pointer = (CHAR *) first_unused_memory; + + status = tx_thread_create(&control_thread, "control thread", control_thread_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + /* The remaining threads are created by the control thread. */ + for (i = 0; i <= WAITERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Waiter Priority Change Test................... ERROR #1\n"); + test_control_return(1); + } +} + + +/* The owner takes the mutex and parks, and the waiters block on it. */ + +static void mutex_thread_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +static void check(UINT condition, UINT error) +{ + + if (condition == TX_FALSE) + { + + printf("ERROR #%u\n", error); + test_control_return(1); + } +} + + +static void start_thread(TX_THREAD *thread_ptr, UINT stack, UINT priority) +{ + + check(tx_thread_create(thread_ptr, "test thread", mutex_thread_entry, 0, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); +} + + +/* The owner at 15 holds the mutex, with waiter 0 at 20 and waiter 1 at 8, so the owner + runs at 8. Each step changes the priority of one waiter, and the owner must follow + the highest priority left waiting. */ + +static void control_thread_entry(ULONG thread_input) +{ + +static const UINT step_waiter[STEPS] = {0, 0, 1, 0}; +static const UINT step_priority[STEPS] = {5, 12, 20, 20}; +static const UINT step_highest_waiting[STEPS] = {5, 8, 12, 20}; +static const UINT step_owner_priority[STEPS] = {5, 8, 12, 15}; +UINT old_priority; +UINT i; + + + /* Inform user. */ + printf("Running Mutex Inherit Waiter Priority Change Test................... "); + + start_thread(&owner_thread, WAITERS, 15); + WAIT_UNTIL((mutex_0.tx_mutex_owner == &owner_thread) && (owner_thread.tx_thread_state == TX_SUSPENDED)) + start_thread(&waiter[0], 0, 20); + start_thread(&waiter[1], 1, 8); + WAIT_UNTIL(mutex_0.tx_mutex_suspended_count == WAITERS) + check(owner_thread.tx_thread_priority == 8, 10); + + for (i = 0; i < STEPS; i++) + { + tx_thread_priority_change(&waiter[step_waiter[i]], step_priority[i], &old_priority); + check(waiter[step_waiter[i]].tx_thread_state == TX_MUTEX_SUSP, 20 + i); + check(mutex_0.tx_mutex_highest_priority_waiting == step_highest_waiting[i], 30 + i); + check(owner_thread.tx_thread_priority == step_owner_priority[i], 40 + i); + } + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +}