From 09c8547a3f37c50e14639447b0ddff2c44c02380 Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Wed, 14 Oct 2026 01:58:24 +0800 Subject: [PATCH 1/2] 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); +} From 44fcf703bb577502851e7d15fb8c69ee2e27d24f Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Sat, 17 Oct 2026 12:42:56 +0800 Subject: [PATCH 2/2] Limited the waiter priority update to priority inheritance mutexes _tx_thread_priority_change called _tx_mutex_inherit_priority_update for a thread waiting on any mutex. For a TX_NO_INHERIT mutex with several waiters, the helper's call to _tx_mutex_prioritize moved the changed waiter to the front of the suspension list, so changing a waiter's priority changed the FIFO order in which the waiters obtain the mutex. The update now runs only when the mutex was created with TX_INHERIT, and the helper's header states that requirement. Applied to common and common_smp. threadx_mutex_inherit_waiter_priority_change_test now also checks: - that three waiters on a TX_NO_INHERIT mutex obtain it in the order they blocked after the last one is raised above the others. Without the fix it fails with ERROR #62 in both suites. - a ready owner, moved 10, 15, 8 and back to 15 by changes to its waiter's priority, and that the owner runs before the call returns when the caller is below the owner's new priority. - an owner holding two TX_INHERIT mutexes and one TX_NO_INHERIT mutex, including a step that leaves the owner's priority unchanged. - two cases set up by hand: a TX_MUTEX_SUSP thread whose cleanup has already been cleared, and the helper run on a mutex with no waiters left. The test passes in every tx and smp build configuration. Merged coverage of the changed lines is 100% of lines and branches, except the TX_MISRA_ENABLE retry loop around _tx_mutex_prioritize, which cannot iterate because _tx_misra_status_get always returns TX_SUCCESS. The same loops in tx_mutex_put.c are also half covered. --- common/src/tx_mutex_cleanup.c | 4 + common/src/tx_thread_priority_change.c | 15 +- common_smp/src/tx_mutex_cleanup.c | 4 + common_smp/src/tx_thread_priority_change.c | 15 +- ...utex_inherit_waiter_priority_change_test.c | 316 +++++++++++++++--- ...utex_inherit_waiter_priority_change_test.c | 316 +++++++++++++++--- 6 files changed, 584 insertions(+), 86 deletions(-) diff --git a/common/src/tx_mutex_cleanup.c b/common/src/tx_mutex_cleanup.c index 3adb21ada..a4bbbb309 100644 --- a/common/src/tx_mutex_cleanup.c +++ b/common/src/tx_mutex_cleanup.c @@ -330,6 +330,10 @@ UINT status; /* preemption disabled and interrupts enabled, as for */ /* _tx_mutex_priority_change. Otherwise interrupts must be disabled. */ /* */ +/* The mutex must support priority inheritance. Its suspension list */ +/* is reordered by priority here, which would break the FIFO order */ +/* the waiters of any other mutex are served in. */ +/* */ /* INPUT */ /* */ /* mutex_ptr Pointer to mutex control block */ diff --git a/common/src/tx_thread_priority_change.c b/common/src/tx_thread_priority_change.c index 8479526d1..d4865babe 100644 --- a/common/src/tx_thread_priority_change.c +++ b/common/src/tx_thread_priority_change.c @@ -124,13 +124,22 @@ UINT original_priority; } /* Determine if the thread is waiting on a mutex. */ + mutex_ptr = TX_NULL; 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. */ + /* Yes, pickup the mutex. */ mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + } + + /* Determine if the mutex supports priority inheritance. Only then can its owner + have inherited the old priority of this thread, or have to inherit the new one. + The suspension list of any other mutex is left alone, so it stays in FIFO + order. */ + if ((mutex_ptr != TX_NULL) && (mutex_ptr -> tx_mutex_inherit == TX_TRUE)) + { + + /* Yes, the update function pointer is set, since a mutex has been created. */ #ifndef TX_NOT_INTERRUPTABLE diff --git a/common_smp/src/tx_mutex_cleanup.c b/common_smp/src/tx_mutex_cleanup.c index 3adb21ada..a4bbbb309 100644 --- a/common_smp/src/tx_mutex_cleanup.c +++ b/common_smp/src/tx_mutex_cleanup.c @@ -330,6 +330,10 @@ UINT status; /* preemption disabled and interrupts enabled, as for */ /* _tx_mutex_priority_change. Otherwise interrupts must be disabled. */ /* */ +/* The mutex must support priority inheritance. Its suspension list */ +/* is reordered by priority here, which would break the FIFO order */ +/* the waiters of any other mutex are served in. */ +/* */ /* INPUT */ /* */ /* mutex_ptr Pointer to mutex control block */ diff --git a/common_smp/src/tx_thread_priority_change.c b/common_smp/src/tx_thread_priority_change.c index a13d6477d..504b37b6a 100644 --- a/common_smp/src/tx_thread_priority_change.c +++ b/common_smp/src/tx_thread_priority_change.c @@ -178,13 +178,22 @@ UINT status; } /* Determine if the thread is waiting on a mutex. */ + mutex_ptr = TX_NULL; 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. */ + /* Yes, pickup the mutex. */ mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + } + + /* Determine if the mutex supports priority inheritance. Only then can its owner + have inherited the old priority of this thread, or have to inherit the new one. + The suspension list of any other mutex is left alone, so it stays in FIFO + order. */ + if ((mutex_ptr != TX_NULL) && (mutex_ptr -> tx_mutex_inherit == TX_TRUE)) + { + + /* Yes, the update function pointer is set, since a mutex has been created. */ #ifndef TX_NOT_INTERRUPTABLE 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 index 2444d0db6..4ec02e07e 100644 --- a/test/smp/regression/threadx_mutex_inherit_waiter_priority_change_test.c +++ b/test/smp/regression/threadx_mutex_inherit_waiter_priority_change_test.c @@ -11,10 +11,16 @@ /* 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. */ + must lower the owner to the highest priority still waiting on any priority + inheritance mutex it holds, or to its own. This is checked with the owner + suspended and with the owner ready. Changing the priority of a thread waiting on a + mutex without priority inheritance must neither change the owner's priority nor + the FIFO order in which the waiters obtain the mutex. Two cases that scheduling on + a single core cannot produce are set up by hand at the end. */ #include #include "tx_api.h" +#include "tx_mutex.h" /* Wait a tick at a time, for at most 200 ticks, until a condition holds. A thread the @@ -22,15 +28,31 @@ #define WAIT_UNTIL(condition) { UINT ticks = 0; while ((!(condition)) && (ticks < 200)) { tx_thread_sleep(1); ticks++; } } -#define WAITERS 2 -#define STEPS 4 +#define INHERIT_WAITERS 3 +#define INHERIT_STEPS 6 +#define FIFO_WAITERS 3 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; +static TX_THREAD inherit_owner; +static TX_THREAD inherit_waiter[INHERIT_WAITERS]; +static TX_THREAD fifo_owner; +static TX_THREAD fifo_waiter[FIFO_WAITERS]; +static TX_THREAD ready_owner; +static TX_THREAD ready_waiter; +static CHAR *stack_pointer; + +/* The owner of the first part holds two priority inheritance mutexes and one without + priority inheritance, and the waiters of that part wait on the first two. */ +static TX_MUTEX inherit_mutex[2]; +static TX_MUTEX plain_mutex; +static TX_MUTEX fifo_mutex; +static TX_MUTEX ready_mutex; + +static volatile UINT fifo_order[FIFO_WAITERS]; +static volatile UINT fifo_count; +static volatile UINT ready_owner_release; +static volatile UINT ready_waiter_obtained; /* Define thread prototypes. */ @@ -54,7 +76,6 @@ void threadx_mutex_inherit_waiter_priority_change_application_define(void *fi UINT status; CHAR *pointer; -UINT i; /* Put first available memory address into a character pointer. */ @@ -66,13 +87,13 @@ UINT i; 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; - } + stack_pointer = pointer; - status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + status += tx_mutex_create(&inherit_mutex[0], "inherit mutex 0", TX_INHERIT); + status += tx_mutex_create(&inherit_mutex[1], "inherit mutex 1", TX_INHERIT); + status += tx_mutex_create(&plain_mutex, "plain mutex", TX_NO_INHERIT); + status += tx_mutex_create(&fifo_mutex, "fifo mutex", TX_NO_INHERIT); + status += tx_mutex_create(&ready_mutex, "ready mutex", TX_INHERIT); /* Check status. */ if (status != TX_SUCCESS) @@ -84,12 +105,80 @@ UINT i; } -/* The owner takes the mutex and parks, and the waiters block on it. */ +/* The owner of the first part takes its three mutexes and parks. */ + +static void inherit_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&inherit_mutex[0], TX_WAIT_FOREVER); + tx_mutex_get(&inherit_mutex[1], TX_WAIT_FOREVER); + tx_mutex_get(&plain_mutex, TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +/* A waiter of the first part blocks on the mutex its input selects. */ + +static void inherit_waiter_entry(ULONG thread_input) +{ + + tx_mutex_get(&inherit_mutex[thread_input], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +/* The owner of the FIFO part takes the mutex, parks, and releases the mutex when it + is resumed. */ + +static void fifo_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&fifo_mutex, TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); + tx_mutex_put(&fifo_mutex); + tx_thread_suspend(tx_thread_identify()); +} + + +/* A waiter of the FIFO part records its input when it obtains the mutex, and passes + the mutex on. */ + +static void fifo_waiter_entry(ULONG thread_input) +{ + + tx_mutex_get(&fifo_mutex, TX_WAIT_FOREVER); + fifo_order[fifo_count] = (UINT) thread_input; + fifo_count++; + tx_mutex_put(&fifo_mutex); + tx_thread_suspend(tx_thread_identify()); +} + + +/* The owner of the ready part takes the mutex and stays ready until it is told to + release the mutex. */ + +static void ready_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&ready_mutex, TX_WAIT_FOREVER); + while (ready_owner_release == TX_FALSE) + { + + /* Spin, so the owner is preempted rather than suspended. */ + } + tx_mutex_put(&ready_mutex); + tx_thread_suspend(tx_thread_identify()); +} + + +/* The waiter of the ready part records that it obtained the mutex. */ -static void mutex_thread_entry(ULONG thread_input) +static void ready_waiter_entry(ULONG thread_input) { - tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_mutex_get(&ready_mutex, TX_WAIT_FOREVER); + ready_waiter_obtained = TX_TRUE; + tx_mutex_put(&ready_mutex); tx_thread_suspend(tx_thread_identify()); } @@ -106,46 +195,193 @@ static void check(UINT condition, UINT error) } -static void start_thread(TX_THREAD *thread_ptr, UINT stack, UINT priority) +static void start_thread(TX_THREAD *thread_ptr, VOID (*entry)(ULONG), ULONG input, UINT priority) { - check(tx_thread_create(thread_ptr, "test thread", mutex_thread_entry, 0, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + check(tx_thread_create(thread_ptr, "test thread", entry, input, stack_pointer, TEST_STACK_SIZE_PRINTF, priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); + stack_pointer = stack_pointer + TEST_STACK_SIZE_PRINTF; } -/* 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. */ +/* The owner at 15 holds both priority inheritance mutexes, with waiters 0 at 20 and 1 + at 8 on the first and waiter 2 at 13 on the second, so the owner runs at 8. It also + holds a mutex without priority inheritance, which must not count. Each step changes + the priority of one waiter, and the owner must follow the highest priority left + waiting on either mutex. */ -static void control_thread_entry(ULONG thread_input) +static void inherit_suspended_owner(void) { -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}; +static const UINT step_waiter[INHERIT_STEPS] = {0, 0, 1, 0, 0, 2}; +static const UINT step_priority[INHERIT_STEPS] = {5, 12, 20, 18, 16, 20}; +static const UINT step_highest_waiting[INHERIT_STEPS] = {5, 8, 12, 18, 16, 20}; +static const UINT step_owner_priority[INHERIT_STEPS] = {5, 8, 12, 13, 13, 15}; +static const UINT waiter_mutex[INHERIT_WAITERS] = {0, 0, 1}; UINT old_priority; UINT i; - /* Inform user. */ - printf("Running Mutex Inherit Waiter Priority Change Test................... "); + start_thread(&inherit_owner, inherit_owner_entry, 0, 15); + WAIT_UNTIL((plain_mutex.tx_mutex_owner == &inherit_owner) && (inherit_owner.tx_thread_state == TX_SUSPENDED)) + start_thread(&inherit_waiter[0], inherit_waiter_entry, 0, 20); + start_thread(&inherit_waiter[1], inherit_waiter_entry, 0, 8); + start_thread(&inherit_waiter[2], inherit_waiter_entry, 1, 13); + WAIT_UNTIL((inherit_mutex[0].tx_mutex_suspended_count == 2) && (inherit_mutex[1].tx_mutex_suspended_count == 1)) + check(inherit_owner.tx_thread_priority == 8, 10); + + for (i = 0; i < INHERIT_STEPS; i++) + { + tx_thread_priority_change(&inherit_waiter[step_waiter[i]], step_priority[i], &old_priority); + check(inherit_waiter[step_waiter[i]].tx_thread_state == TX_MUTEX_SUSP, 20 + i); + check(inherit_mutex[waiter_mutex[step_waiter[i]]].tx_mutex_highest_priority_waiting == step_highest_waiting[i], 30 + i); + check(inherit_owner.tx_thread_priority == step_owner_priority[i], 40 + i); + check(inherit_owner.tx_thread_state == TX_SUSPENDED, 50 + i); + } +} - 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++) +/* The owner at 15 holds a mutex without priority inheritance, with waiters 0 at 20, + 1 at 18 and 2 at 16 blocked in that order. Raising waiter 2 above the owner and + the other waiters must leave the owner's priority and the order of the waiters as + they are, so the waiters obtain the mutex in the order they blocked. */ + +static void no_inherit_fifo_order(void) +{ + +UINT old_priority; +UINT i; + + + start_thread(&fifo_owner, fifo_owner_entry, 0, 15); + WAIT_UNTIL((fifo_mutex.tx_mutex_owner == &fifo_owner) && (fifo_owner.tx_thread_state == TX_SUSPENDED)) + for (i = 0; i < FIFO_WAITERS; 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); + start_thread(&fifo_waiter[i], fifo_waiter_entry, i, 20 - (2 * i)); + WAIT_UNTIL(fifo_mutex.tx_mutex_suspended_count == (i + 1)) } + check(fifo_mutex.tx_mutex_suspended_count == FIFO_WAITERS, 60); + + tx_thread_priority_change(&fifo_waiter[2], 5, &old_priority); + check(fifo_waiter[2].tx_thread_state == TX_MUTEX_SUSP, 61); + check(fifo_mutex.tx_mutex_suspension_list == &fifo_waiter[0], 62); + check(fifo_owner.tx_thread_priority == 15, 63); + + /* Let the owner release the mutex, and the waiters pass it on. */ + tx_thread_resume(&fifo_owner); + WAIT_UNTIL(fifo_count == FIFO_WAITERS) + check(fifo_count == FIFO_WAITERS, 64); + for (i = 0; i < FIFO_WAITERS; i++) + { + check(fifo_order[i] == i, 65 + i); + } +} + + +/* The owner at 15 holds a priority inheritance mutex and stays ready, with the waiter + at 10 blocked on it, so the owner is ready at 10. Lowering and raising the waiter + must move the ready owner to 15, to 8 and back to 15. Raising the waiter while + the caller runs below the new priority of the owner must let the owner run before + the call returns. */ + +static void inherit_ready_owner(void) +{ + +UINT old_priority; + + + start_thread(&ready_owner, ready_owner_entry, 0, 15); + WAIT_UNTIL(ready_mutex.tx_mutex_owner == &ready_owner) + start_thread(&ready_waiter, ready_waiter_entry, 0, 10); + WAIT_UNTIL(ready_mutex.tx_mutex_suspended_count == 1) + check(ready_owner.tx_thread_state == TX_READY, 70); + check(ready_owner.tx_thread_priority == 10, 71); + + /* Lower the waiter below the owner. */ + tx_thread_priority_change(&ready_waiter, 20, &old_priority); + check(ready_waiter.tx_thread_state == TX_MUTEX_SUSP, 72); + check(ready_mutex.tx_mutex_highest_priority_waiting == 20, 73); + check(ready_owner.tx_thread_state == TX_READY, 74); + check(ready_owner.tx_thread_priority == 15, 75); + + /* Raise the waiter above the owner. */ + tx_thread_priority_change(&ready_waiter, 8, &old_priority); + check(ready_mutex.tx_mutex_highest_priority_waiting == 8, 76); + check(ready_owner.tx_thread_state == TX_READY, 77); + check(ready_owner.tx_thread_priority == 8, 78); + + /* Lower the waiter below the owner again. */ + tx_thread_priority_change(&ready_waiter, 20, &old_priority); + check(ready_owner.tx_thread_state == TX_READY, 79); + check(ready_owner.tx_thread_priority == 15, 80); + + /* Run below the owner's next priority but above its own, so only inheritance + lets the owner run, and raise the waiter again. The owner releases the mutex + as soon as it runs, and the waiter obtains it. */ + tx_thread_priority_change(&control_thread, 12, &old_priority); + ready_owner_release = TX_TRUE; + tx_thread_priority_change(&ready_waiter, 5, &old_priority); + check(ready_waiter_obtained == TX_TRUE, 81); + check(ready_owner.tx_thread_priority == 15, 82); + tx_thread_priority_change(&control_thread, 1, &old_priority); +} + + +/* A thread in TX_MUTEX_SUSP without a cleanup routine has already been taken off the + suspension list by tx_mutex_put or the mutex cleanup, which another core can be + doing while the priority changes, and changing its priority must leave the mutex + alone. And the waiter an update is made for can leave the suspension list before + the update runs, for example through tx_thread_wait_abort from an ISR, so the + update must handle a mutex that nobody waits on any more. */ + +static void hand_set_up_cases(void) +{ + +UINT old_priority; +UINT saved_state; +VOID *saved_control_block; + + + /* Make waiter 0 of the FIFO part, parked without a cleanup routine, look like a + thread being taken off the suspension list of the first mutex of the first + part, and change its priority. */ + saved_state = fifo_waiter[0].tx_thread_state; + saved_control_block = fifo_waiter[0].tx_thread_suspend_control_block; + fifo_waiter[0].tx_thread_state = TX_MUTEX_SUSP; + fifo_waiter[0].tx_thread_suspend_cleanup = TX_NULL; + fifo_waiter[0].tx_thread_suspend_control_block = (VOID *) &inherit_mutex[0]; + tx_thread_priority_change(&fifo_waiter[0], 2, &old_priority); + fifo_waiter[0].tx_thread_state = saved_state; + fifo_waiter[0].tx_thread_suspend_control_block = saved_control_block; + check(fifo_waiter[0].tx_thread_priority == 2, 90); + check(inherit_mutex[0].tx_mutex_highest_priority_waiting == 16, 91); + check(inherit_owner.tx_thread_priority == 15, 92); + + /* Raise waiter 2 of the first part above the owner, so the owner inherits its + priority, then take the waiter off the suspension list of the second mutex and + update that mutex. The owner must return to its own priority, since the + highest priority still waiting on its mutexes is 16. */ + tx_thread_priority_change(&inherit_waiter[2], 10, &old_priority); + check(inherit_owner.tx_thread_priority == 10, 93); + tx_thread_wait_abort(&inherit_waiter[2]); + _tx_mutex_inherit_priority_update(&inherit_mutex[1]); + check(inherit_mutex[1].tx_mutex_suspension_list == TX_NULL, 94); + check(inherit_mutex[1].tx_mutex_highest_priority_waiting == ((UINT) TX_MAX_PRIORITIES), 95); + check(inherit_owner.tx_thread_priority == 15, 96); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + + /* Inform user. */ + printf("Running Mutex Inherit Waiter Priority Change Test................... "); + + inherit_suspended_owner(); + no_inherit_fifo_order(); + inherit_ready_owner(); + hand_set_up_cases(); /* Successful test. */ printf("SUCCESS!\n"); 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 index 2444d0db6..4ec02e07e 100644 --- a/test/tx/regression/threadx_mutex_inherit_waiter_priority_change_test.c +++ b/test/tx/regression/threadx_mutex_inherit_waiter_priority_change_test.c @@ -11,10 +11,16 @@ /* 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. */ + must lower the owner to the highest priority still waiting on any priority + inheritance mutex it holds, or to its own. This is checked with the owner + suspended and with the owner ready. Changing the priority of a thread waiting on a + mutex without priority inheritance must neither change the owner's priority nor + the FIFO order in which the waiters obtain the mutex. Two cases that scheduling on + a single core cannot produce are set up by hand at the end. */ #include #include "tx_api.h" +#include "tx_mutex.h" /* Wait a tick at a time, for at most 200 ticks, until a condition holds. A thread the @@ -22,15 +28,31 @@ #define WAIT_UNTIL(condition) { UINT ticks = 0; while ((!(condition)) && (ticks < 200)) { tx_thread_sleep(1); ticks++; } } -#define WAITERS 2 -#define STEPS 4 +#define INHERIT_WAITERS 3 +#define INHERIT_STEPS 6 +#define FIFO_WAITERS 3 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; +static TX_THREAD inherit_owner; +static TX_THREAD inherit_waiter[INHERIT_WAITERS]; +static TX_THREAD fifo_owner; +static TX_THREAD fifo_waiter[FIFO_WAITERS]; +static TX_THREAD ready_owner; +static TX_THREAD ready_waiter; +static CHAR *stack_pointer; + +/* The owner of the first part holds two priority inheritance mutexes and one without + priority inheritance, and the waiters of that part wait on the first two. */ +static TX_MUTEX inherit_mutex[2]; +static TX_MUTEX plain_mutex; +static TX_MUTEX fifo_mutex; +static TX_MUTEX ready_mutex; + +static volatile UINT fifo_order[FIFO_WAITERS]; +static volatile UINT fifo_count; +static volatile UINT ready_owner_release; +static volatile UINT ready_waiter_obtained; /* Define thread prototypes. */ @@ -54,7 +76,6 @@ void threadx_mutex_inherit_waiter_priority_change_application_define(void *fi UINT status; CHAR *pointer; -UINT i; /* Put first available memory address into a character pointer. */ @@ -66,13 +87,13 @@ UINT i; 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; - } + stack_pointer = pointer; - status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + status += tx_mutex_create(&inherit_mutex[0], "inherit mutex 0", TX_INHERIT); + status += tx_mutex_create(&inherit_mutex[1], "inherit mutex 1", TX_INHERIT); + status += tx_mutex_create(&plain_mutex, "plain mutex", TX_NO_INHERIT); + status += tx_mutex_create(&fifo_mutex, "fifo mutex", TX_NO_INHERIT); + status += tx_mutex_create(&ready_mutex, "ready mutex", TX_INHERIT); /* Check status. */ if (status != TX_SUCCESS) @@ -84,12 +105,80 @@ UINT i; } -/* The owner takes the mutex and parks, and the waiters block on it. */ +/* The owner of the first part takes its three mutexes and parks. */ + +static void inherit_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&inherit_mutex[0], TX_WAIT_FOREVER); + tx_mutex_get(&inherit_mutex[1], TX_WAIT_FOREVER); + tx_mutex_get(&plain_mutex, TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +/* A waiter of the first part blocks on the mutex its input selects. */ + +static void inherit_waiter_entry(ULONG thread_input) +{ + + tx_mutex_get(&inherit_mutex[thread_input], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +/* The owner of the FIFO part takes the mutex, parks, and releases the mutex when it + is resumed. */ + +static void fifo_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&fifo_mutex, TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); + tx_mutex_put(&fifo_mutex); + tx_thread_suspend(tx_thread_identify()); +} + + +/* A waiter of the FIFO part records its input when it obtains the mutex, and passes + the mutex on. */ + +static void fifo_waiter_entry(ULONG thread_input) +{ + + tx_mutex_get(&fifo_mutex, TX_WAIT_FOREVER); + fifo_order[fifo_count] = (UINT) thread_input; + fifo_count++; + tx_mutex_put(&fifo_mutex); + tx_thread_suspend(tx_thread_identify()); +} + + +/* The owner of the ready part takes the mutex and stays ready until it is told to + release the mutex. */ + +static void ready_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&ready_mutex, TX_WAIT_FOREVER); + while (ready_owner_release == TX_FALSE) + { + + /* Spin, so the owner is preempted rather than suspended. */ + } + tx_mutex_put(&ready_mutex); + tx_thread_suspend(tx_thread_identify()); +} + + +/* The waiter of the ready part records that it obtained the mutex. */ -static void mutex_thread_entry(ULONG thread_input) +static void ready_waiter_entry(ULONG thread_input) { - tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_mutex_get(&ready_mutex, TX_WAIT_FOREVER); + ready_waiter_obtained = TX_TRUE; + tx_mutex_put(&ready_mutex); tx_thread_suspend(tx_thread_identify()); } @@ -106,46 +195,193 @@ static void check(UINT condition, UINT error) } -static void start_thread(TX_THREAD *thread_ptr, UINT stack, UINT priority) +static void start_thread(TX_THREAD *thread_ptr, VOID (*entry)(ULONG), ULONG input, UINT priority) { - check(tx_thread_create(thread_ptr, "test thread", mutex_thread_entry, 0, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + check(tx_thread_create(thread_ptr, "test thread", entry, input, stack_pointer, TEST_STACK_SIZE_PRINTF, priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); + stack_pointer = stack_pointer + TEST_STACK_SIZE_PRINTF; } -/* 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. */ +/* The owner at 15 holds both priority inheritance mutexes, with waiters 0 at 20 and 1 + at 8 on the first and waiter 2 at 13 on the second, so the owner runs at 8. It also + holds a mutex without priority inheritance, which must not count. Each step changes + the priority of one waiter, and the owner must follow the highest priority left + waiting on either mutex. */ -static void control_thread_entry(ULONG thread_input) +static void inherit_suspended_owner(void) { -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}; +static const UINT step_waiter[INHERIT_STEPS] = {0, 0, 1, 0, 0, 2}; +static const UINT step_priority[INHERIT_STEPS] = {5, 12, 20, 18, 16, 20}; +static const UINT step_highest_waiting[INHERIT_STEPS] = {5, 8, 12, 18, 16, 20}; +static const UINT step_owner_priority[INHERIT_STEPS] = {5, 8, 12, 13, 13, 15}; +static const UINT waiter_mutex[INHERIT_WAITERS] = {0, 0, 1}; UINT old_priority; UINT i; - /* Inform user. */ - printf("Running Mutex Inherit Waiter Priority Change Test................... "); + start_thread(&inherit_owner, inherit_owner_entry, 0, 15); + WAIT_UNTIL((plain_mutex.tx_mutex_owner == &inherit_owner) && (inherit_owner.tx_thread_state == TX_SUSPENDED)) + start_thread(&inherit_waiter[0], inherit_waiter_entry, 0, 20); + start_thread(&inherit_waiter[1], inherit_waiter_entry, 0, 8); + start_thread(&inherit_waiter[2], inherit_waiter_entry, 1, 13); + WAIT_UNTIL((inherit_mutex[0].tx_mutex_suspended_count == 2) && (inherit_mutex[1].tx_mutex_suspended_count == 1)) + check(inherit_owner.tx_thread_priority == 8, 10); + + for (i = 0; i < INHERIT_STEPS; i++) + { + tx_thread_priority_change(&inherit_waiter[step_waiter[i]], step_priority[i], &old_priority); + check(inherit_waiter[step_waiter[i]].tx_thread_state == TX_MUTEX_SUSP, 20 + i); + check(inherit_mutex[waiter_mutex[step_waiter[i]]].tx_mutex_highest_priority_waiting == step_highest_waiting[i], 30 + i); + check(inherit_owner.tx_thread_priority == step_owner_priority[i], 40 + i); + check(inherit_owner.tx_thread_state == TX_SUSPENDED, 50 + i); + } +} - 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++) +/* The owner at 15 holds a mutex without priority inheritance, with waiters 0 at 20, + 1 at 18 and 2 at 16 blocked in that order. Raising waiter 2 above the owner and + the other waiters must leave the owner's priority and the order of the waiters as + they are, so the waiters obtain the mutex in the order they blocked. */ + +static void no_inherit_fifo_order(void) +{ + +UINT old_priority; +UINT i; + + + start_thread(&fifo_owner, fifo_owner_entry, 0, 15); + WAIT_UNTIL((fifo_mutex.tx_mutex_owner == &fifo_owner) && (fifo_owner.tx_thread_state == TX_SUSPENDED)) + for (i = 0; i < FIFO_WAITERS; 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); + start_thread(&fifo_waiter[i], fifo_waiter_entry, i, 20 - (2 * i)); + WAIT_UNTIL(fifo_mutex.tx_mutex_suspended_count == (i + 1)) } + check(fifo_mutex.tx_mutex_suspended_count == FIFO_WAITERS, 60); + + tx_thread_priority_change(&fifo_waiter[2], 5, &old_priority); + check(fifo_waiter[2].tx_thread_state == TX_MUTEX_SUSP, 61); + check(fifo_mutex.tx_mutex_suspension_list == &fifo_waiter[0], 62); + check(fifo_owner.tx_thread_priority == 15, 63); + + /* Let the owner release the mutex, and the waiters pass it on. */ + tx_thread_resume(&fifo_owner); + WAIT_UNTIL(fifo_count == FIFO_WAITERS) + check(fifo_count == FIFO_WAITERS, 64); + for (i = 0; i < FIFO_WAITERS; i++) + { + check(fifo_order[i] == i, 65 + i); + } +} + + +/* The owner at 15 holds a priority inheritance mutex and stays ready, with the waiter + at 10 blocked on it, so the owner is ready at 10. Lowering and raising the waiter + must move the ready owner to 15, to 8 and back to 15. Raising the waiter while + the caller runs below the new priority of the owner must let the owner run before + the call returns. */ + +static void inherit_ready_owner(void) +{ + +UINT old_priority; + + + start_thread(&ready_owner, ready_owner_entry, 0, 15); + WAIT_UNTIL(ready_mutex.tx_mutex_owner == &ready_owner) + start_thread(&ready_waiter, ready_waiter_entry, 0, 10); + WAIT_UNTIL(ready_mutex.tx_mutex_suspended_count == 1) + check(ready_owner.tx_thread_state == TX_READY, 70); + check(ready_owner.tx_thread_priority == 10, 71); + + /* Lower the waiter below the owner. */ + tx_thread_priority_change(&ready_waiter, 20, &old_priority); + check(ready_waiter.tx_thread_state == TX_MUTEX_SUSP, 72); + check(ready_mutex.tx_mutex_highest_priority_waiting == 20, 73); + check(ready_owner.tx_thread_state == TX_READY, 74); + check(ready_owner.tx_thread_priority == 15, 75); + + /* Raise the waiter above the owner. */ + tx_thread_priority_change(&ready_waiter, 8, &old_priority); + check(ready_mutex.tx_mutex_highest_priority_waiting == 8, 76); + check(ready_owner.tx_thread_state == TX_READY, 77); + check(ready_owner.tx_thread_priority == 8, 78); + + /* Lower the waiter below the owner again. */ + tx_thread_priority_change(&ready_waiter, 20, &old_priority); + check(ready_owner.tx_thread_state == TX_READY, 79); + check(ready_owner.tx_thread_priority == 15, 80); + + /* Run below the owner's next priority but above its own, so only inheritance + lets the owner run, and raise the waiter again. The owner releases the mutex + as soon as it runs, and the waiter obtains it. */ + tx_thread_priority_change(&control_thread, 12, &old_priority); + ready_owner_release = TX_TRUE; + tx_thread_priority_change(&ready_waiter, 5, &old_priority); + check(ready_waiter_obtained == TX_TRUE, 81); + check(ready_owner.tx_thread_priority == 15, 82); + tx_thread_priority_change(&control_thread, 1, &old_priority); +} + + +/* A thread in TX_MUTEX_SUSP without a cleanup routine has already been taken off the + suspension list by tx_mutex_put or the mutex cleanup, which another core can be + doing while the priority changes, and changing its priority must leave the mutex + alone. And the waiter an update is made for can leave the suspension list before + the update runs, for example through tx_thread_wait_abort from an ISR, so the + update must handle a mutex that nobody waits on any more. */ + +static void hand_set_up_cases(void) +{ + +UINT old_priority; +UINT saved_state; +VOID *saved_control_block; + + + /* Make waiter 0 of the FIFO part, parked without a cleanup routine, look like a + thread being taken off the suspension list of the first mutex of the first + part, and change its priority. */ + saved_state = fifo_waiter[0].tx_thread_state; + saved_control_block = fifo_waiter[0].tx_thread_suspend_control_block; + fifo_waiter[0].tx_thread_state = TX_MUTEX_SUSP; + fifo_waiter[0].tx_thread_suspend_cleanup = TX_NULL; + fifo_waiter[0].tx_thread_suspend_control_block = (VOID *) &inherit_mutex[0]; + tx_thread_priority_change(&fifo_waiter[0], 2, &old_priority); + fifo_waiter[0].tx_thread_state = saved_state; + fifo_waiter[0].tx_thread_suspend_control_block = saved_control_block; + check(fifo_waiter[0].tx_thread_priority == 2, 90); + check(inherit_mutex[0].tx_mutex_highest_priority_waiting == 16, 91); + check(inherit_owner.tx_thread_priority == 15, 92); + + /* Raise waiter 2 of the first part above the owner, so the owner inherits its + priority, then take the waiter off the suspension list of the second mutex and + update that mutex. The owner must return to its own priority, since the + highest priority still waiting on its mutexes is 16. */ + tx_thread_priority_change(&inherit_waiter[2], 10, &old_priority); + check(inherit_owner.tx_thread_priority == 10, 93); + tx_thread_wait_abort(&inherit_waiter[2]); + _tx_mutex_inherit_priority_update(&inherit_mutex[1]); + check(inherit_mutex[1].tx_mutex_suspension_list == TX_NULL, 94); + check(inherit_mutex[1].tx_mutex_highest_priority_waiting == ((UINT) TX_MAX_PRIORITIES), 95); + check(inherit_owner.tx_thread_priority == 15, 96); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + + /* Inform user. */ + printf("Running Mutex Inherit Waiter Priority Change Test................... "); + + inherit_suspended_owner(); + no_inherit_fifo_order(); + inherit_ready_owner(); + hand_set_up_cases(); /* Successful test. */ printf("SUCCESS!\n");