From 2820d6d61ce16d7073c0232163b3b8875694b573 Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Wed, 14 Oct 2026 01:58:34 +0800 Subject: [PATCH] Added opt-in priority inheritance through chains of mutexes tx_mutex_get raised the owner of a TX_INHERIT mutex and stopped. If that owner was itself blocked on a second inheriting mutex, the second owner never inherited the priority: with H blocked on m1, owned by L1, blocked on m2, owned by L2, L2 stayed at L1's old priority and H waited behind every thread between the two. When TX_MUTEX_INHERIT_CHAIN_MAX is defined, tx_mutex_get passes the inherited priority on through a new internal function, _tx_mutex_priority_chain. While the thread it last raised is waiting on an inheriting mutex, it updates that mutex's highest waiting priority and raises the mutex owner with _tx_mutex_priority_change, which handles ready and suspended owners alike, for at most TX_MUTEX_INHERIT_CHAIN_MAX mutexes. It stops at the first owner already at the priority or higher, which also ends it on a lock cycle. The walk is iterative and only raises priorities. It is opt-in, so existing builds keep their worst-case tx_mutex_get time. Documented in tx_user_sample.h. Applied to common and common_smp. threadx_mutex_inherit_chain_test covers a chain of four suspended owners, which also checks the length limit, a chain ending at a ready owner, and a lock cycle, and checks the unchained behaviour when the option is not defined. Added to both suites, with TX_MUTEX_INHERIT_CHAIN_MAX=2 in default_build_coverage so the walk is built and covered. Without the fix it fails with ERROR #12 in both; with it, the mutex tests pass in both suites' default_build_coverage configuration and the new test passes in disable_notify_callbacks_build, where the option is not defined. The changed sources build cleanly under -Wall -Wextra -Werror with TX_NOT_INTERRUPTABLE, TX_MISRA_ENABLE and TX_DISABLE_PREEMPTION_THRESHOLD, with and without the option. --- common/inc/tx_mutex.h | 3 + common/inc/tx_user_sample.h | 11 +- common/src/tx_mutex_get.c | 13 + common/src/tx_mutex_priority_change.c | 121 ++++++++ common_smp/inc/tx_mutex.h | 3 + common_smp/inc/tx_user_sample.h | 11 +- common_smp/src/tx_mutex_get.c | 13 + common_smp/src/tx_mutex_priority_change.c | 121 ++++++++ test/smp/cmake/CMakeLists.txt | 2 +- test/smp/cmake/regression/CMakeLists.txt | 1 + test/smp/regression/testcontrol.c | 2 + .../threadx_mutex_inherit_chain_test.c | 290 ++++++++++++++++++ test/tx/cmake/CMakeLists.txt | 2 +- test/tx/cmake/regression/CMakeLists.txt | 1 + test/tx/regression/testcontrol.c | 2 + .../threadx_mutex_inherit_chain_test.c | 290 ++++++++++++++++++ 16 files changed, 882 insertions(+), 4 deletions(-) create mode 100644 test/smp/regression/threadx_mutex_inherit_chain_test.c create mode 100644 test/tx/regression/threadx_mutex_inherit_chain_test.c diff --git a/common/inc/tx_mutex.h b/common/inc/tx_mutex.h index b0b46c2f9..8f7ec84b3 100644 --- a/common/inc/tx_mutex.h +++ b/common/inc/tx_mutex.h @@ -81,6 +81,9 @@ VOID _tx_mutex_initialize(VOID); /* Define internal mutex management function prototypes. */ +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr, UINT new_priority); +#endif 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); diff --git a/common/inc/tx_user_sample.h b/common/inc/tx_user_sample.h index 676ff38cd..4847cc352 100644 --- a/common/inc/tx_user_sample.h +++ b/common/inc/tx_user_sample.h @@ -11,7 +11,6 @@ /* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ /** */ @@ -262,6 +261,16 @@ #define TX_MUTEX_ENABLE_PERFORMANCE_INFO */ +/* Determine if priority inheritance follows chains of mutexes. When a thread that inherits + a priority is itself waiting on a priority inheritance mutex, the owner of that mutex does + not inherit the priority unless the following is defined. Its value is the maximum number + of mutexes followed along one chain, which bounds the time tx_mutex_get spends passing + the priority on. */ + +/* +#define TX_MUTEX_INHERIT_CHAIN_MAX 8 +*/ + /* Determine if queue performance gathering is required by the application. When the following is defined, ThreadX gathers various queue performance information. */ diff --git a/common/src/tx_mutex_get.c b/common/src/tx_mutex_get.c index 402a0f0d0..ee32006ba 100644 --- a/common/src/tx_mutex_get.c +++ b/common/src/tx_mutex_get.c @@ -60,6 +60,7 @@ /* _tx_thread_system_suspend Suspend thread service */ /* _tx_thread_system_ni_suspend Non-interruptable suspend thread */ /* _tx_mutex_priority_change Inherit thread priority */ +/* _tx_mutex_priority_chain Inherit along mutex chain */ /* */ /* CALLED BY */ /* */ @@ -309,6 +310,12 @@ UINT status; of the current thread. */ _tx_mutex_priority_change(mutex_owner, thread_ptr -> tx_thread_priority); +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + + /* Pass the priority on to the owner of any mutex the owner is waiting on. */ + _tx_mutex_priority_chain(mutex_owner, thread_ptr -> tx_thread_priority); +#endif + #ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO /* Increment the total mutex priority inheritance counter. */ @@ -368,6 +375,12 @@ UINT status; of the current thread. */ _tx_mutex_priority_change(mutex_owner, thread_ptr -> tx_thread_priority); +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + + /* Pass the priority on to the owner of any mutex the owner is waiting on. */ + _tx_mutex_priority_chain(mutex_owner, thread_ptr -> tx_thread_priority); +#endif + #ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO /* Increment the total mutex priority inheritance counter. */ diff --git a/common/src/tx_mutex_priority_change.c b/common/src/tx_mutex_priority_change.c index 846e1a543..865b46594 100644 --- a/common/src/tx_mutex_priority_change.c +++ b/common/src/tx_mutex_priority_change.c @@ -320,3 +320,124 @@ UINT map_index; } } + +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_mutex_priority_chain PORTABLE C */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function passes an inherited priority along a chain of */ +/* priority inheritance mutexes. While the thread is waiting on such a */ +/* mutex, the mutex owner inherits the priority and the owner becomes */ +/* the next thread in the chain. At most TX_MUTEX_INHERIT_CHAIN_MAX */ +/* mutexes are followed. The walk ends at the first owner already at */ +/* the priority or higher, so it also ends on a lock cycle. */ +/* */ +/* INPUT */ +/* */ +/* thread_ptr Thread that inherited priority */ +/* new_priority Inherited priority */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_mutex_priority_change Inherit thread priority */ +/* */ +/* CALLED BY */ +/* */ +/* _tx_mutex_get Inherit priority */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr, UINT new_priority) +{ + +#ifndef TX_NOT_INTERRUPTABLE +TX_INTERRUPT_SAVE_AREA +#endif + +TX_MUTEX *mutex_ptr; +TX_THREAD *owner_ptr; +UINT chain_length; + + + /* Follow the chain for at most TX_MUTEX_INHERIT_CHAIN_MAX mutexes. */ + chain_length = ((UINT) 0); + while ((thread_ptr != TX_NULL) && (chain_length < ((UINT) TX_MUTEX_INHERIT_CHAIN_MAX))) + { + + /* Default to the end of the chain. */ + owner_ptr = TX_NULL; + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Determine if this thread is waiting on a mutex. */ + if ((thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) && (thread_ptr -> tx_thread_suspend_cleanup != TX_NULL)) + { + + /* Pickup the mutex the thread is waiting on. */ + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + + /* Determine if priority inheritance is in effect. */ + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + + /* Determine if this is the highest priority to raise for this mutex. */ + if (mutex_ptr -> tx_mutex_highest_priority_waiting > new_priority) + { + + /* Remember this priority. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = new_priority; + } + + /* Pickup the mutex owner. */ + owner_ptr = mutex_ptr -> tx_mutex_owner; + + /* Determine if we have to update inherit priority level of the mutex owner. */ + if (owner_ptr -> tx_thread_inherit_priority > new_priority) + { + + /* Remember the new priority inheritance priority. */ + owner_ptr -> tx_thread_inherit_priority = new_priority; + } + + /* Determine if the owner already runs at the priority or higher. */ + if (owner_ptr -> tx_thread_priority <= new_priority) + { + + /* Yes, the chain ends here. */ + owner_ptr = TX_NULL; + } + } + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Determine if there is an owner to raise. */ + if (owner_ptr != TX_NULL) + { + + /* Raise the owner's priority to the inherited priority. */ + _tx_mutex_priority_change(owner_ptr, new_priority); + } + + /* Move to the next thread in the chain. */ + thread_ptr = owner_ptr; + chain_length++; + } +} +#endif diff --git a/common_smp/inc/tx_mutex.h b/common_smp/inc/tx_mutex.h index b0b46c2f9..8f7ec84b3 100644 --- a/common_smp/inc/tx_mutex.h +++ b/common_smp/inc/tx_mutex.h @@ -81,6 +81,9 @@ VOID _tx_mutex_initialize(VOID); /* Define internal mutex management function prototypes. */ +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr, UINT new_priority); +#endif 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); diff --git a/common_smp/inc/tx_user_sample.h b/common_smp/inc/tx_user_sample.h index 1514a0dbe..0a68fb20e 100644 --- a/common_smp/inc/tx_user_sample.h +++ b/common_smp/inc/tx_user_sample.h @@ -11,7 +11,6 @@ /* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ /** */ @@ -255,6 +254,16 @@ #define TX_MUTEX_ENABLE_PERFORMANCE_INFO */ +/* Determine if priority inheritance follows chains of mutexes. When a thread that inherits + a priority is itself waiting on a priority inheritance mutex, the owner of that mutex does + not inherit the priority unless the following is defined. Its value is the maximum number + of mutexes followed along one chain, which bounds the time tx_mutex_get spends passing + the priority on. */ + +/* +#define TX_MUTEX_INHERIT_CHAIN_MAX 8 +*/ + /* Determine if queue performance gathering is required by the application. When the following is defined, ThreadX gathers various queue performance information. */ diff --git a/common_smp/src/tx_mutex_get.c b/common_smp/src/tx_mutex_get.c index 402a0f0d0..ee32006ba 100644 --- a/common_smp/src/tx_mutex_get.c +++ b/common_smp/src/tx_mutex_get.c @@ -60,6 +60,7 @@ /* _tx_thread_system_suspend Suspend thread service */ /* _tx_thread_system_ni_suspend Non-interruptable suspend thread */ /* _tx_mutex_priority_change Inherit thread priority */ +/* _tx_mutex_priority_chain Inherit along mutex chain */ /* */ /* CALLED BY */ /* */ @@ -309,6 +310,12 @@ UINT status; of the current thread. */ _tx_mutex_priority_change(mutex_owner, thread_ptr -> tx_thread_priority); +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + + /* Pass the priority on to the owner of any mutex the owner is waiting on. */ + _tx_mutex_priority_chain(mutex_owner, thread_ptr -> tx_thread_priority); +#endif + #ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO /* Increment the total mutex priority inheritance counter. */ @@ -368,6 +375,12 @@ UINT status; of the current thread. */ _tx_mutex_priority_change(mutex_owner, thread_ptr -> tx_thread_priority); +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + + /* Pass the priority on to the owner of any mutex the owner is waiting on. */ + _tx_mutex_priority_chain(mutex_owner, thread_ptr -> tx_thread_priority); +#endif + #ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO /* Increment the total mutex priority inheritance counter. */ diff --git a/common_smp/src/tx_mutex_priority_change.c b/common_smp/src/tx_mutex_priority_change.c index 8722a8fb8..f33d9e081 100644 --- a/common_smp/src/tx_mutex_priority_change.c +++ b/common_smp/src/tx_mutex_priority_change.c @@ -484,3 +484,124 @@ UINT finished; #endif } + +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_mutex_priority_chain PORTABLE C */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function passes an inherited priority along a chain of */ +/* priority inheritance mutexes. While the thread is waiting on such a */ +/* mutex, the mutex owner inherits the priority and the owner becomes */ +/* the next thread in the chain. At most TX_MUTEX_INHERIT_CHAIN_MAX */ +/* mutexes are followed. The walk ends at the first owner already at */ +/* the priority or higher, so it also ends on a lock cycle. */ +/* */ +/* INPUT */ +/* */ +/* thread_ptr Thread that inherited priority */ +/* new_priority Inherited priority */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_mutex_priority_change Inherit thread priority */ +/* */ +/* CALLED BY */ +/* */ +/* _tx_mutex_get Inherit priority */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr, UINT new_priority) +{ + +#ifndef TX_NOT_INTERRUPTABLE +TX_INTERRUPT_SAVE_AREA +#endif + +TX_MUTEX *mutex_ptr; +TX_THREAD *owner_ptr; +UINT chain_length; + + + /* Follow the chain for at most TX_MUTEX_INHERIT_CHAIN_MAX mutexes. */ + chain_length = ((UINT) 0); + while ((thread_ptr != TX_NULL) && (chain_length < ((UINT) TX_MUTEX_INHERIT_CHAIN_MAX))) + { + + /* Default to the end of the chain. */ + owner_ptr = TX_NULL; + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Determine if this thread is waiting on a mutex. */ + if ((thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) && (thread_ptr -> tx_thread_suspend_cleanup != TX_NULL)) + { + + /* Pickup the mutex the thread is waiting on. */ + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + + /* Determine if priority inheritance is in effect. */ + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + + /* Determine if this is the highest priority to raise for this mutex. */ + if (mutex_ptr -> tx_mutex_highest_priority_waiting > new_priority) + { + + /* Remember this priority. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = new_priority; + } + + /* Pickup the mutex owner. */ + owner_ptr = mutex_ptr -> tx_mutex_owner; + + /* Determine if we have to update inherit priority level of the mutex owner. */ + if (owner_ptr -> tx_thread_inherit_priority > new_priority) + { + + /* Remember the new priority inheritance priority. */ + owner_ptr -> tx_thread_inherit_priority = new_priority; + } + + /* Determine if the owner already runs at the priority or higher. */ + if (owner_ptr -> tx_thread_priority <= new_priority) + { + + /* Yes, the chain ends here. */ + owner_ptr = TX_NULL; + } + } + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Determine if there is an owner to raise. */ + if (owner_ptr != TX_NULL) + { + + /* Raise the owner's priority to the inherited priority. */ + _tx_mutex_priority_change(owner_ptr, new_priority); + } + + /* Move to the next thread in the chain. */ + thread_ptr = owner_ptr; + chain_length++; + } +} +#endif diff --git a/test/smp/cmake/CMakeLists.txt b/test/smp/cmake/CMakeLists.txt index 3a0d9638a..9ac0f2c68 100644 --- a/test/smp/cmake/CMakeLists.txt +++ b/test/smp/cmake/CMakeLists.txt @@ -31,7 +31,7 @@ endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}.") -set(default_build_coverage TX_QUEUE_MESSAGE_MAX_SIZE=32) +set(default_build_coverage TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_MUTEX_INHERIT_CHAIN_MAX=2) set(disable_notify_callbacks_build TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_DISABLE_NOTIFY_CALLBACKS) set(stack_checking_build TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_ENABLE_STACK_CHECKING) set(stack_checking_rand_fill_build TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_ENABLE_STACK_CHECKING TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) diff --git a/test/smp/cmake/regression/CMakeLists.txt b/test/smp/cmake/regression/CMakeLists.txt index cdf0c5859..93db84c88 100644 --- a/test/smp/cmake/regression/CMakeLists.txt +++ b/test/smp/cmake/regression/CMakeLists.txt @@ -49,6 +49,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c ${SOURCE_DIR}/threadx_mutex_no_preemption_test.c ${SOURCE_DIR}/threadx_mutex_preemption_test.c + ${SOURCE_DIR}/threadx_mutex_inherit_chain_test.c ${SOURCE_DIR}/threadx_mutex_priority_inheritance_test.c ${SOURCE_DIR}/threadx_mutex_proritize_test.c ${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c diff --git a/test/smp/regression/testcontrol.c b/test/smp/regression/testcontrol.c index c964dc03a..8f7771e09 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -196,6 +196,7 @@ void threadx_event_flag_information_application_define(void *); void threadx_interrupt_control_application_define(void *); void threadx_mutex_basic_application_define(void *); +void threadx_mutex_inherit_chain_application_define(void *); void threadx_mutex_delete_application_define(void *); void threadx_mutex_preemption_application_define(void *); void threadx_mutext_no_preemption_application_define(void *); @@ -343,6 +344,7 @@ TEST_ENTRY test_control_tests[] = threadx_interrupt_control_application_define, threadx_mutex_basic_application_define, + threadx_mutex_inherit_chain_application_define, threadx_mutex_delete_application_define, threadx_mutex_preemption_application_define, threadx_mutext_no_preemption_application_define, diff --git a/test/smp/regression/threadx_mutex_inherit_chain_test.c b/test/smp/regression/threadx_mutex_inherit_chain_test.c new file mode 100644 index 000000000..24922dda4 --- /dev/null +++ b/test/smp/regression/threadx_mutex_inherit_chain_test.c @@ -0,0 +1,290 @@ +/*************************************************************************** + * 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 priority inheritance through a chain of mutexes: owner 0 holds + mutex 0 and waits on mutex 1, held by owner 1, and so on. When a thread at 5 then + blocks on mutex 0, TX_MUTEX_INHERIT_CHAIN_MAX decides how far along the chain the + priority is passed. Without it, only owner 0 inherits. The chain must also reach + an owner that is ready, and must end on a lock cycle. */ + +#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 OWNERS 4 + + +static TX_THREAD control_thread; +static TX_THREAD waiter_thread; +static TX_THREAD owner[OWNERS]; +static TX_MUTEX mutex[OWNERS]; +static CHAR *thread_stack[OWNERS + 1]; +static volatile UINT owner_spin; + + +/* 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_chain_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 and deleted by each scenario. */ + for (i = 0; i <= OWNERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + for (i = 0; i < OWNERS; i++) + { + status += tx_mutex_create(&mutex[i], "chain mutex", TX_INHERIT); + } + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Chain Test.................................... ERROR #1\n"); + test_control_return(1); + } +} + + +/* The waiter blocks on mutex 0. */ + +static void waiter_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[0], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +/* Owner i takes mutex i and then waits on mutex i + 1. The last owner parks. */ + +static void chain_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); + if (thread_input + 1 < OWNERS) + { + tx_mutex_get(&mutex[thread_input + 1], TX_WAIT_FOREVER); + } + tx_thread_suspend(tx_thread_identify()); +} + + +/* Takes the mutex and stays ready until told to stop. */ + +static void spinning_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); + while (owner_spin) + { + } + tx_thread_suspend(tx_thread_identify()); +} + + +/* Owner i takes mutex i, parks, and once resumed waits on the other mutex. */ + +static void cycle_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); + tx_mutex_get(&mutex[1 - thread_input], 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, VOID (*entry)(ULONG), ULONG input, UINT stack, UINT priority) +{ + + check(tx_thread_create(thread_ptr, "test thread", entry, input, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); +} + + +/* Start the waiter on mutex 0, which owner 0 holds. */ + +static void start_waiter(void) +{ + + start_thread(&waiter_thread, waiter_entry, 0, OWNERS, 5); + WAIT_UNTIL(waiter_thread.tx_thread_state == TX_MUTEX_SUSP) +} + + +/* Delete the waiter and the first count owners. */ + +static void stop_threads(UINT count) +{ + +UINT i; + + + tx_thread_terminate(&waiter_thread); + tx_thread_delete(&waiter_thread); + for (i = count; i > 0; i--) + { + tx_thread_terminate(&owner[i - 1]); + tx_thread_delete(&owner[i - 1]); + } +} + + +/* Determine if owner i inherits the waiter's priority, which owner 0 does directly and + owner i only if the chain reaches i - 1 mutexes past owner 0. */ + +static UINT inherits(UINT i) +{ + +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + return(i <= (UINT) TX_MUTEX_INHERIT_CHAIN_MAX); +#else + return(i == 0); +#endif +} + + +/* Owners at 20, 19, 18 and 17, each but the last waiting on the next one's mutex. + Each waits on a higher-priority owner, so nothing is inherited until the waiter + blocks. */ + +static void chain_of_suspended_owners(UINT error) +{ + +UINT i; + + + /* Start from the end of the chain, so each mutex is owned before it is waited on. */ + for (i = OWNERS; i > 0; i--) + { + start_thread(&owner[i - 1], chain_owner_entry, i - 1, i - 1, 20 - (i - 1)); + WAIT_UNTIL((mutex[i - 1].tx_mutex_owner == &owner[i - 1]) && + (owner[i - 1].tx_thread_state == ((i < OWNERS) ? TX_MUTEX_SUSP : TX_SUSPENDED))) + } + check(owner[OWNERS - 1].tx_thread_priority == 20 - (OWNERS - 1), error); + + start_waiter(); + for (i = 0; i < OWNERS; i++) + { + check(owner[i].tx_thread_priority == (inherits(i) ? 5 : 20 - i), error + 1 + i); + } + + stop_threads(OWNERS); +} + + +/* Owner 1 at 20 holds mutex 1 and stays ready. Owner 0 at 15 holds mutex 0 and waits + on mutex 1, so owner 1 runs at 15 until the waiter blocks. */ + +static void chain_to_ready_owner(UINT error) +{ + + owner_spin = 1; + start_thread(&owner[1], spinning_owner_entry, 1, 1, 20); + WAIT_UNTIL(mutex[1].tx_mutex_owner == &owner[1]) + start_thread(&owner[0], chain_owner_entry, 0, 0, 15); + WAIT_UNTIL(owner[0].tx_thread_state == TX_MUTEX_SUSP) + check(owner[1].tx_thread_state == TX_READY, error); + check(owner[1].tx_thread_priority == 15, error + 1); + + start_waiter(); + check(owner[0].tx_thread_priority == 5, error + 2); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 15), error + 3); + + owner_spin = 0; + WAIT_UNTIL(owner[1].tx_thread_state == TX_SUSPENDED) + stop_threads(2); +} + + +/* Owners at 15 and 20 each hold one mutex and wait on the other's, a deadlock. The + walk started by the waiter must end. */ + +static void chain_with_cycle(UINT error) +{ + + start_thread(&owner[0], cycle_owner_entry, 0, 0, 15); + start_thread(&owner[1], cycle_owner_entry, 1, 1, 20); + WAIT_UNTIL((mutex[0].tx_mutex_owner == &owner[0]) && (owner[0].tx_thread_state == TX_SUSPENDED) && + (mutex[1].tx_mutex_owner == &owner[1]) && (owner[1].tx_thread_state == TX_SUSPENDED)) + tx_thread_resume(&owner[0]); + tx_thread_resume(&owner[1]); + WAIT_UNTIL((owner[0].tx_thread_state == TX_MUTEX_SUSP) && (owner[1].tx_thread_state == TX_MUTEX_SUSP)) + + start_waiter(); + check(owner[0].tx_thread_priority == 5, error); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 15), error + 1); + + stop_threads(2); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + /* Inform user. */ + printf("Running Mutex Inherit Chain Test.................................... "); + + chain_of_suspended_owners(10); + chain_to_ready_owner(20); + chain_with_cycle(30); + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +} diff --git a/test/tx/cmake/CMakeLists.txt b/test/tx/cmake/CMakeLists.txt index 2f9c9b9f4..b00eda918 100644 --- a/test/tx/cmake/CMakeLists.txt +++ b/test/tx/cmake/CMakeLists.txt @@ -31,7 +31,7 @@ endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}.") -set(default_build_coverage TX_QUEUE_MESSAGE_MAX_SIZE=32) +set(default_build_coverage TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_MUTEX_INHERIT_CHAIN_MAX=2) set(disable_notify_callbacks_build TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_DISABLE_NOTIFY_CALLBACKS) set(stack_checking_build TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_ENABLE_STACK_CHECKING) set(stack_checking_rand_fill_build TX_QUEUE_MESSAGE_MAX_SIZE=32 TX_ENABLE_STACK_CHECKING TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) diff --git a/test/tx/cmake/regression/CMakeLists.txt b/test/tx/cmake/regression/CMakeLists.txt index 6b94a8576..04ae2403f 100644 --- a/test/tx/cmake/regression/CMakeLists.txt +++ b/test/tx/cmake/regression/CMakeLists.txt @@ -46,6 +46,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c ${SOURCE_DIR}/threadx_mutex_no_preemption_test.c ${SOURCE_DIR}/threadx_mutex_preemption_test.c + ${SOURCE_DIR}/threadx_mutex_inherit_chain_test.c ${SOURCE_DIR}/threadx_mutex_priority_inheritance_test.c ${SOURCE_DIR}/threadx_mutex_proritize_test.c ${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c diff --git a/test/tx/regression/testcontrol.c b/test/tx/regression/testcontrol.c index bec50ee76..bb2f7dea0 100644 --- a/test/tx/regression/testcontrol.c +++ b/test/tx/regression/testcontrol.c @@ -168,6 +168,7 @@ void threadx_event_flag_information_application_define(void *); void threadx_interrupt_control_application_define(void *); void threadx_mutex_basic_application_define(void *); +void threadx_mutex_inherit_chain_application_define(void *); void threadx_mutex_delete_application_define(void *); void threadx_mutex_preemption_application_define(void *); void threadx_mutext_no_preemption_application_define(void *); @@ -287,6 +288,7 @@ TEST_ENTRY test_control_tests[] = threadx_interrupt_control_application_define, threadx_mutex_basic_application_define, + threadx_mutex_inherit_chain_application_define, threadx_mutex_delete_application_define, threadx_mutex_preemption_application_define, threadx_mutext_no_preemption_application_define, diff --git a/test/tx/regression/threadx_mutex_inherit_chain_test.c b/test/tx/regression/threadx_mutex_inherit_chain_test.c new file mode 100644 index 000000000..24922dda4 --- /dev/null +++ b/test/tx/regression/threadx_mutex_inherit_chain_test.c @@ -0,0 +1,290 @@ +/*************************************************************************** + * 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 priority inheritance through a chain of mutexes: owner 0 holds + mutex 0 and waits on mutex 1, held by owner 1, and so on. When a thread at 5 then + blocks on mutex 0, TX_MUTEX_INHERIT_CHAIN_MAX decides how far along the chain the + priority is passed. Without it, only owner 0 inherits. The chain must also reach + an owner that is ready, and must end on a lock cycle. */ + +#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 OWNERS 4 + + +static TX_THREAD control_thread; +static TX_THREAD waiter_thread; +static TX_THREAD owner[OWNERS]; +static TX_MUTEX mutex[OWNERS]; +static CHAR *thread_stack[OWNERS + 1]; +static volatile UINT owner_spin; + + +/* 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_chain_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 and deleted by each scenario. */ + for (i = 0; i <= OWNERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + for (i = 0; i < OWNERS; i++) + { + status += tx_mutex_create(&mutex[i], "chain mutex", TX_INHERIT); + } + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Chain Test.................................... ERROR #1\n"); + test_control_return(1); + } +} + + +/* The waiter blocks on mutex 0. */ + +static void waiter_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[0], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + + +/* Owner i takes mutex i and then waits on mutex i + 1. The last owner parks. */ + +static void chain_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); + if (thread_input + 1 < OWNERS) + { + tx_mutex_get(&mutex[thread_input + 1], TX_WAIT_FOREVER); + } + tx_thread_suspend(tx_thread_identify()); +} + + +/* Takes the mutex and stays ready until told to stop. */ + +static void spinning_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); + while (owner_spin) + { + } + tx_thread_suspend(tx_thread_identify()); +} + + +/* Owner i takes mutex i, parks, and once resumed waits on the other mutex. */ + +static void cycle_owner_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); + tx_mutex_get(&mutex[1 - thread_input], 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, VOID (*entry)(ULONG), ULONG input, UINT stack, UINT priority) +{ + + check(tx_thread_create(thread_ptr, "test thread", entry, input, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); +} + + +/* Start the waiter on mutex 0, which owner 0 holds. */ + +static void start_waiter(void) +{ + + start_thread(&waiter_thread, waiter_entry, 0, OWNERS, 5); + WAIT_UNTIL(waiter_thread.tx_thread_state == TX_MUTEX_SUSP) +} + + +/* Delete the waiter and the first count owners. */ + +static void stop_threads(UINT count) +{ + +UINT i; + + + tx_thread_terminate(&waiter_thread); + tx_thread_delete(&waiter_thread); + for (i = count; i > 0; i--) + { + tx_thread_terminate(&owner[i - 1]); + tx_thread_delete(&owner[i - 1]); + } +} + + +/* Determine if owner i inherits the waiter's priority, which owner 0 does directly and + owner i only if the chain reaches i - 1 mutexes past owner 0. */ + +static UINT inherits(UINT i) +{ + +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + return(i <= (UINT) TX_MUTEX_INHERIT_CHAIN_MAX); +#else + return(i == 0); +#endif +} + + +/* Owners at 20, 19, 18 and 17, each but the last waiting on the next one's mutex. + Each waits on a higher-priority owner, so nothing is inherited until the waiter + blocks. */ + +static void chain_of_suspended_owners(UINT error) +{ + +UINT i; + + + /* Start from the end of the chain, so each mutex is owned before it is waited on. */ + for (i = OWNERS; i > 0; i--) + { + start_thread(&owner[i - 1], chain_owner_entry, i - 1, i - 1, 20 - (i - 1)); + WAIT_UNTIL((mutex[i - 1].tx_mutex_owner == &owner[i - 1]) && + (owner[i - 1].tx_thread_state == ((i < OWNERS) ? TX_MUTEX_SUSP : TX_SUSPENDED))) + } + check(owner[OWNERS - 1].tx_thread_priority == 20 - (OWNERS - 1), error); + + start_waiter(); + for (i = 0; i < OWNERS; i++) + { + check(owner[i].tx_thread_priority == (inherits(i) ? 5 : 20 - i), error + 1 + i); + } + + stop_threads(OWNERS); +} + + +/* Owner 1 at 20 holds mutex 1 and stays ready. Owner 0 at 15 holds mutex 0 and waits + on mutex 1, so owner 1 runs at 15 until the waiter blocks. */ + +static void chain_to_ready_owner(UINT error) +{ + + owner_spin = 1; + start_thread(&owner[1], spinning_owner_entry, 1, 1, 20); + WAIT_UNTIL(mutex[1].tx_mutex_owner == &owner[1]) + start_thread(&owner[0], chain_owner_entry, 0, 0, 15); + WAIT_UNTIL(owner[0].tx_thread_state == TX_MUTEX_SUSP) + check(owner[1].tx_thread_state == TX_READY, error); + check(owner[1].tx_thread_priority == 15, error + 1); + + start_waiter(); + check(owner[0].tx_thread_priority == 5, error + 2); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 15), error + 3); + + owner_spin = 0; + WAIT_UNTIL(owner[1].tx_thread_state == TX_SUSPENDED) + stop_threads(2); +} + + +/* Owners at 15 and 20 each hold one mutex and wait on the other's, a deadlock. The + walk started by the waiter must end. */ + +static void chain_with_cycle(UINT error) +{ + + start_thread(&owner[0], cycle_owner_entry, 0, 0, 15); + start_thread(&owner[1], cycle_owner_entry, 1, 1, 20); + WAIT_UNTIL((mutex[0].tx_mutex_owner == &owner[0]) && (owner[0].tx_thread_state == TX_SUSPENDED) && + (mutex[1].tx_mutex_owner == &owner[1]) && (owner[1].tx_thread_state == TX_SUSPENDED)) + tx_thread_resume(&owner[0]); + tx_thread_resume(&owner[1]); + WAIT_UNTIL((owner[0].tx_thread_state == TX_MUTEX_SUSP) && (owner[1].tx_thread_state == TX_MUTEX_SUSP)) + + start_waiter(); + check(owner[0].tx_thread_priority == 5, error); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 15), error + 1); + + stop_threads(2); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + /* Inform user. */ + printf("Running Mutex Inherit Chain Test.................................... "); + + chain_of_suspended_owners(10); + chain_to_ready_owner(20); + chain_with_cycle(30); + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +}