From 2820d6d61ce16d7073c0232163b3b8875694b573 Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Wed, 14 Oct 2026 01:58:34 +0800 Subject: [PATCH 1/2] 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); +} From f5fc49ca9abbc81cde91c85c3eb7ae19331000c1 Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Sat, 17 Oct 2026 22:47:35 +0800 Subject: [PATCH 2/2] fix(mutex): restore inherited priorities through bounded chains Recompute inheritance after waiter and ownership changes, handle contained cycles, and correct the SMP priority-change fast path. Add restoration regressions and document bounds and validation. --- common/inc/tx_mutex.h | 5 +- common/inc/tx_thread.h | 2 + common/inc/tx_user_sample.h | 23 +- common/src/tx_mutex_cleanup.c | 13 + common/src/tx_mutex_create.c | 3 +- common/src/tx_mutex_get.c | 48 ++- common/src/tx_mutex_priority_change.c | 320 ++++++++++----- common/src/tx_mutex_put.c | 120 +----- common/src/tx_thread_initialize.c | 3 + common/src/tx_thread_priority_change.c | 29 ++ common_smp/inc/tx_mutex.h | 5 +- common_smp/inc/tx_thread.h | 4 +- common_smp/inc/tx_user_sample.h | 23 +- common_smp/src/tx_mutex_cleanup.c | 13 + common_smp/src/tx_mutex_create.c | 3 +- common_smp/src/tx_mutex_get.c | 48 ++- common_smp/src/tx_mutex_priority_change.c | 320 ++++++++++----- common_smp/src/tx_mutex_put.c | 120 +----- common_smp/src/tx_thread_initialize.c | 3 + common_smp/src/tx_thread_priority_change.c | 33 +- docs/mutex-inherit-chain-restoration.adoc | 369 ++++++++++++++++++ test/smp/cmake/regression/CMakeLists.txt | 1 + test/smp/regression/testcontrol.c | 4 +- .../threadx_mutex_inherit_chain_test.c | 302 +++++++++++++- .../threadx_mutex_inherit_wait_exit_test.c | 232 +++++++++++ test/tx/cmake/regression/CMakeLists.txt | 1 + test/tx/cmake/riscv/regression/CMakeLists.txt | 2 + test/tx/regression/testcontrol.c | 4 +- .../threadx_mutex_inherit_chain_test.c | 302 +++++++++++++- .../threadx_mutex_inherit_wait_exit_test.c | 232 +++++++++++ 30 files changed, 2126 insertions(+), 461 deletions(-) create mode 100644 docs/mutex-inherit-chain-restoration.adoc create mode 100644 test/smp/regression/threadx_mutex_inherit_wait_exit_test.c create mode 100644 test/tx/regression/threadx_mutex_inherit_wait_exit_test.c diff --git a/common/inc/tx_mutex.h b/common/inc/tx_mutex.h index 8f7ec84b3..218da41cf 100644 --- a/common/inc/tx_mutex.h +++ b/common/inc/tx_mutex.h @@ -81,9 +81,8 @@ 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_priority_chain(TX_THREAD *thread_ptr); +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr); 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_thread.h b/common/inc/tx_thread.h index 5ba8b3f54..c902d1af4 100644 --- a/common/inc/tx_thread.h +++ b/common/inc/tx_thread.h @@ -388,6 +388,8 @@ THREAD_DECLARE volatile UINT _tx_thread_preempt_disable; termination. This pointer is setup during mutex initialization. */ THREAD_DECLARE VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Optional mutex inheritance update, installed by mutex creation. */ +THREAD_DECLARE VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); /* Define the global build options variable. This contains a bit map representing diff --git a/common/inc/tx_user_sample.h b/common/inc/tx_user_sample.h index 4847cc352..b71bdcc37 100644 --- a/common/inc/tx_user_sample.h +++ b/common/inc/tx_user_sample.h @@ -9,8 +9,6 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -/* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ /** */ @@ -261,11 +259,22 @@ #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. */ +/* Enable bounded, bidirectional priority inheritance through mutex chains. + Define a nonnegative integer K to visit the first owner and at most K further + waiting-mutex edges (K + 1 distinct owners); zero retains a single owner. + Get, wait exit, waiter priority change, release and transfer use the same bound. + A non-inheriting mutex or inactive mutex wait ends the walk. Cycles wholly + contained in the visited path are recomputed from base priorities and outside + waiters, without retaining their own obsolete boosts. A cycle extending beyond + the bound can still retain an outside cached contribution; configure K to cover + the application's longest possible cycle if cycle restoration is required. + Work is O(N*N + N*W + M), for N <= K + 1 visited owners, W waiters and M owned + mutexes across those owners, plus up to N scheduler priority changes. All graph + processing holds interrupt/SMP protection with preemption disabled; the bound + alone does not bound list lengths or total interrupt latency. Stack scratch is + (K + 1)*(sizeof(TX_THREAD *) + 2*sizeof(UINT)), with no allocation or recursion. + When undefined, get retains its existing single-level path and restoration + scans only the changed waiter list and the first owner's cached mutex list. */ /* #define TX_MUTEX_INHERIT_CHAIN_MAX 8 diff --git a/common/src/tx_mutex_cleanup.c b/common/src/tx_mutex_cleanup.c index d32f69fc0..830fadcac 100644 --- a/common/src/tx_mutex_cleanup.c +++ b/common/src/tx_mutex_cleanup.c @@ -80,6 +80,11 @@ TX_THREAD *next_thread; TX_THREAD *previous_thread; +#ifdef TX_NOT_INTERRUPTABLE + /* Suspension sequences are not used in the non-interruptable kernel. */ + (VOID) suspension_sequence; +#endif + #ifndef TX_NOT_INTERRUPTABLE /* Disable interrupts to remove the suspended thread from the mutex. */ @@ -155,6 +160,14 @@ TX_THREAD *previous_thread; } } + /* Remove this waiter's inheritance before it can resume. */ + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + _tx_thread_preempt_disable++; + _tx_mutex_inherit_priority_update(mutex_ptr); + _tx_thread_preempt_disable--; + } + /* Now we need to determine if this cleanup is from a terminate, timeout, or from a wait abort. */ if (thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) diff --git a/common/src/tx_mutex_create.c b/common/src/tx_mutex_create.c index a1f5eba85..497a14b77 100644 --- a/common/src/tx_mutex_create.c +++ b/common/src/tx_mutex_create.c @@ -9,8 +9,6 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -/* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ @@ -91,6 +89,7 @@ TX_MUTEX *previous_mutex; /* Setup the thread mutex release function pointer. */ _tx_thread_mutex_release = &(_tx_mutex_thread_release); + _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. */ diff --git a/common/src/tx_mutex_get.c b/common/src/tx_mutex_get.c index ee32006ba..86a648041 100644 --- a/common/src/tx_mutex_get.c +++ b/common/src/tx_mutex_get.c @@ -60,7 +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 */ +/* _tx_mutex_inherit_priority_update Recompute mutex inheritance */ /* */ /* CALLED BY */ /* */ @@ -75,7 +75,9 @@ TX_INTERRUPT_SAVE_AREA TX_THREAD *thread_ptr; TX_MUTEX *next_mutex; TX_MUTEX *previous_mutex; +#if !defined(TX_MUTEX_INHERIT_CHAIN_MAX) || defined(TX_MUTEX_ENABLE_PERFORMANCE_INFO) TX_THREAD *mutex_owner; +#endif TX_THREAD *next_thread; TX_THREAD *previous_thread; UINT status; @@ -204,7 +206,9 @@ UINT status; /* Prepare for suspension of this thread. */ /* Pickup the mutex owner. */ +#if !defined(TX_MUTEX_INHERIT_CHAIN_MAX) || defined(TX_MUTEX_ENABLE_PERFORMANCE_INFO) mutex_owner = mutex_ptr -> tx_mutex_owner; +#endif #ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO @@ -285,6 +289,18 @@ UINT status; owning the mutex. */ if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) { +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +#ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO + if (mutex_owner -> tx_thread_priority > thread_ptr -> tx_thread_priority) + { + _tx_mutex_performance__priority_inheritance_count++; + mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; + } +#endif + _tx_thread_preempt_disable++; + _tx_mutex_inherit_priority_update(mutex_ptr); + _tx_thread_preempt_disable--; +#else /* Determine if this is the highest priority to raise for this mutex. */ if (mutex_ptr -> tx_mutex_highest_priority_waiting > thread_ptr -> tx_thread_priority) @@ -310,12 +326,6 @@ 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. */ @@ -325,6 +335,7 @@ UINT status; mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; #endif } +#endif } /* Call actual non-interruptable thread suspension routine. */ @@ -343,13 +354,25 @@ UINT status; /* Temporarily disable preemption. */ _tx_thread_preempt_disable++; +#ifndef TX_MUTEX_INHERIT_CHAIN_MAX /* Restore interrupts. */ TX_RESTORE +#endif /* Determine if we need to raise the priority of the thread owning the mutex. */ if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) { +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +#ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO + if (mutex_owner -> tx_thread_priority > thread_ptr -> tx_thread_priority) + { + _tx_mutex_performance__priority_inheritance_count++; + mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; + } +#endif + _tx_mutex_inherit_priority_update(mutex_ptr); +#else /* Determine if this is the highest priority to raise for this mutex. */ if (mutex_ptr -> tx_mutex_highest_priority_waiting > thread_ptr -> tx_thread_priority) @@ -375,12 +398,6 @@ 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. */ @@ -390,8 +407,13 @@ UINT status; mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; #endif } +#endif } +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + TX_RESTORE +#endif + /* Call actual thread suspension routine. */ _tx_thread_system_suspend(thread_ptr); #endif diff --git a/common/src/tx_mutex_priority_change.c b/common/src/tx_mutex_priority_change.c index 865b46594..34d96b574 100644 --- a/common/src/tx_mutex_priority_change.c +++ b/common/src/tx_mutex_priority_change.c @@ -321,123 +321,267 @@ 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_inherit_priority_update */ /* */ -/* _tx_mutex_priority_change Inherit thread priority */ -/* */ -/* CALLED BY */ -/* */ -/* _tx_mutex_get Inherit priority */ +/* Recompute an inheriting mutex after a waiter changes or leaves. */ +/* Caller holds interrupt/SMP protection and disables preemption. */ +/* No list reordering is needed: put prioritizes before transfer. */ /* */ /**************************************************************************/ -VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr, UINT new_priority) +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr) { -#ifndef TX_NOT_INTERRUPTABLE -TX_INTERRUPT_SAVE_AREA -#endif +#ifndef TX_MUTEX_INHERIT_CHAIN_MAX +TX_THREAD *waiter; +UINT remaining; +UINT priority; -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))) + priority = ((UINT) TX_MAX_PRIORITIES); + waiter = mutex_ptr -> tx_mutex_suspension_list; + remaining = mutex_ptr -> tx_mutex_suspended_count; + while (remaining != ((UINT) 0)) { + if (waiter -> tx_thread_priority < priority) + { + priority = waiter -> tx_thread_priority; + } + waiter = waiter -> tx_thread_suspended_next; + remaining--; + } + mutex_ptr -> tx_mutex_highest_priority_waiting = priority; - /* 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)) - { + if (mutex_ptr -> tx_mutex_owner != TX_NULL) + { + _tx_mutex_priority_chain(mutex_ptr -> tx_mutex_owner); + } +} - /* 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) +/**************************************************************************/ +/* */ +/* _tx_mutex_priority_chain */ +/* */ +/* Rebuild inheritance, including decreases, before publishing it. */ +/* With chaining disabled this is the ordinary owned-mutex cache scan. */ +/* Otherwise collect the first owner and at most CHAIN_MAX further */ +/* edges, stopping on a repeated owner or an invalid wait context. */ +/* */ +/* Within the collected path, effective priorities are results, never */ +/* seeds. Seeds are user priorities and waiters outside the path. */ +/* Relaxing its internal edges from these seeds removes stale boosts */ +/* even in a cycle wholly contained in the path. Cycles extending */ +/* beyond the configured bound can still contain stale outside seeds; */ +/* the bound does not promise restoration outside this window. */ +/* */ +/* Caller holds interrupt/SMP protection and disables preemption. */ +/* Scheduler priority changes retain that protection, including in NI. */ +/* Work also depends on owned-mutex and waiter counts (see tx_user.h). */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr) +{ + +TX_MUTEX *mutex_ptr; +UINT priority; +UINT inheritance; +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +TX_THREAD *chain[TX_MUTEX_INHERIT_CHAIN_MAX + 1]; +UINT effective[TX_MUTEX_INHERIT_CHAIN_MAX + 1]; +UINT inherited[TX_MUTEX_INHERIT_CHAIN_MAX + 1]; +TX_THREAD *owner; +TX_THREAD *waiter; +UINT count; +UINT cycle; +UINT i; +UINT j; +UINT pass; +UINT target; +UINT remaining; + + + count = ((UINT) 1); + chain[0] = thread_ptr; + cycle = ((UINT) TX_MUTEX_INHERIT_CHAIN_MAX) + ((UINT) 1); + for (;;) + { + thread_ptr = chain[count - ((UINT) 1)]; + if ((thread_ptr -> tx_thread_state != TX_MUTEX_SUSP) || + (thread_ptr -> tx_thread_suspend_cleanup != &(_tx_mutex_cleanup))) + { + break; + } + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + if ((mutex_ptr == TX_NULL) || (mutex_ptr -> tx_mutex_id != TX_MUTEX_ID) || + (mutex_ptr -> tx_mutex_inherit != TX_TRUE) || + (mutex_ptr -> tx_mutex_ownership_count == ((UINT) 0)) || + (mutex_ptr -> tx_mutex_owner == TX_NULL)) + { + break; + } + owner = mutex_ptr -> tx_mutex_owner; + for (j = 0; j < count; j++) + { + if (chain[j] == owner) { + break; + } + } + if (j < count) + { + cycle = j; + break; + } + if (count == (((UINT) TX_MUTEX_INHERIT_CHAIN_MAX) + ((UINT) 1))) + { + break; + } + chain[count] = owner; + count++; + } - /* Determine if this is the highest priority to raise for this mutex. */ - if (mutex_ptr -> tx_mutex_highest_priority_waiting > new_priority) + /* Seed only from contributions that do not depend on this path. */ + for (i = 0; i < count; i++) + { + inherited[i] = ((UINT) TX_MAX_PRIORITIES); + mutex_ptr = chain[i] -> tx_thread_owned_mutex_list; + if (mutex_ptr != TX_NULL) + { + do + { + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) { - - /* Remember this priority. */ - mutex_ptr -> tx_mutex_highest_priority_waiting = new_priority; + waiter = mutex_ptr -> tx_mutex_suspension_list; + remaining = mutex_ptr -> tx_mutex_suspended_count; + while (remaining != ((UINT) 0)) + { + for (j = 0; j < count; j++) + { + if (chain[j] == waiter) + { + break; + } + } + if ((j == count) && (waiter -> tx_thread_priority < inherited[i])) + { + inherited[i] = waiter -> tx_thread_priority; + } + waiter = waiter -> tx_thread_suspended_next; + remaining--; + } } + mutex_ptr = mutex_ptr -> tx_mutex_owned_next; + } while (mutex_ptr != chain[i] -> tx_thread_owned_mutex_list); + } + effective[i] = chain[i] -> tx_thread_user_priority; + if (inherited[i] < effective[i]) + { + effective[i] = inherited[i]; + } + } - /* 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) + /* Forward order propagates every seed along the tail on the first pass. + A back edge can then move the minimum to an earlier cycle member; + the second pass carries it around the entire cycle. */ + for (pass = 0; pass < ((UINT) 2); pass++) + { + for (i = 0; i < count; i++) + { + target = i + ((UINT) 1); + if (target == count) + { + target = cycle; + } + if (target < count) + { + if (effective[i] < inherited[target]) { - - /* Remember the new priority inheritance priority. */ - owner_ptr -> tx_thread_inherit_priority = new_priority; + inherited[target] = effective[i]; } - - /* Determine if the owner already runs at the priority or higher. */ - if (owner_ptr -> tx_thread_priority <= new_priority) + if (effective[i] < effective[target]) { - - /* Yes, the chain ends here. */ - owner_ptr = TX_NULL; + effective[target] = effective[i]; } } } + } -#ifndef TX_NOT_INTERRUPTABLE - - /* Restore interrupts. */ - TX_RESTORE -#endif - - /* Determine if there is an owner to raise. */ - if (owner_ptr != TX_NULL) + /* Refresh every affected mutex cache from the solved priorities. */ + for (i = 0; i < count; i++) + { + mutex_ptr = chain[i] -> tx_thread_owned_mutex_list; + if (mutex_ptr != TX_NULL) { - - /* Raise the owner's priority to the inherited priority. */ - _tx_mutex_priority_change(owner_ptr, new_priority); + do + { + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + priority = ((UINT) TX_MAX_PRIORITIES); + waiter = mutex_ptr -> tx_mutex_suspension_list; + remaining = mutex_ptr -> tx_mutex_suspended_count; + while (remaining != ((UINT) 0)) + { + inheritance = waiter -> tx_thread_priority; + for (j = 0; j < count; j++) + { + if (chain[j] == waiter) + { + inheritance = effective[j]; + break; + } + } + if (inheritance < priority) + { + priority = inheritance; + } + waiter = waiter -> tx_thread_suspended_next; + remaining--; + } + mutex_ptr -> tx_mutex_highest_priority_waiting = priority; + } + mutex_ptr = mutex_ptr -> tx_mutex_owned_next; + } while (mutex_ptr != chain[i] -> tx_thread_owned_mutex_list); + } + chain[i] -> tx_thread_inherit_priority = inherited[i]; + } + for (i = 0; i < count; i++) + { + if (chain[i] -> tx_thread_priority != effective[i]) + { + _tx_mutex_priority_change(chain[i], effective[i]); } + } +#else - /* Move to the next thread in the chain. */ - thread_ptr = owner_ptr; - chain_length++; + /* Preserve the single-level owned-cache scan when chaining is off. */ + inheritance = ((UINT) TX_MAX_PRIORITIES); + mutex_ptr = thread_ptr -> tx_thread_owned_mutex_list; + if (mutex_ptr != TX_NULL) + { + do + { + if ((mutex_ptr -> tx_mutex_inherit == TX_TRUE) && + (mutex_ptr -> tx_mutex_highest_priority_waiting < inheritance)) + { + inheritance = mutex_ptr -> tx_mutex_highest_priority_waiting; + } + mutex_ptr = mutex_ptr -> tx_mutex_owned_next; + } while (mutex_ptr != thread_ptr -> tx_thread_owned_mutex_list); + } + thread_ptr -> tx_thread_inherit_priority = inheritance; + priority = thread_ptr -> tx_thread_user_priority; + if (inheritance < priority) + { + priority = inheritance; + } + if (thread_ptr -> tx_thread_priority != priority) + { + _tx_mutex_priority_change(thread_ptr, priority); } -} #endif +} diff --git a/common/src/tx_mutex_put.c b/common/src/tx_mutex_put.c index 062472a24..453bba06a 100644 --- a/common/src/tx_mutex_put.c +++ b/common/src/tx_mutex_put.c @@ -86,7 +86,6 @@ TX_THREAD *current_thread; TX_THREAD *next_thread; TX_THREAD *previous_thread; TX_THREAD *suspended_thread; -UINT inheritance_priority; /* Setup status to indicate the processing is not complete. */ @@ -234,75 +233,6 @@ UINT inheritance_priority; old_owner = TX_NULL; old_priority = thread_ptr -> tx_thread_user_priority; - /* Does this mutex support priority inheritance? */ - if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) - { - -#ifndef TX_NOT_INTERRUPTABLE - - /* Temporarily disable preemption. */ - _tx_thread_preempt_disable++; - - /* Restore interrupts. */ - TX_RESTORE -#endif - - /* Default the inheritance priority to disabled. */ - inheritance_priority = ((UINT) TX_MAX_PRIORITIES); - - /* Search the owned mutexes for this thread to determine the highest priority for this - former mutex owner to return to. */ - next_mutex = thread_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 priority to - restore. */ - if (next_mutex -> tx_mutex_highest_priority_waiting < inheritance_priority) - { - - /* Use this priority to return releasing thread to. */ - 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 == thread_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 - - /* Undo the temporarily preemption disable. */ - _tx_thread_preempt_disable--; -#endif - - /* Set the inherit priority to that of the highest priority thread waiting on the mutex. */ - thread_ptr -> tx_thread_inherit_priority = inheritance_priority; - - /* Determine if the inheritance priority is less than the default old priority. */ - if (inheritance_priority < old_priority) - { - - /* Yes, update the old priority. */ - old_priority = inheritance_priority; - } - } - /* Determine if priority inheritance is in effect and there are one or more threads suspended on the mutex. */ if (mutex_ptr -> tx_mutex_suspended_count > ((UINT) 1)) @@ -358,14 +288,9 @@ UINT inheritance_priority; /* No, there are no longer any threads waiting on the mutex. */ -#ifndef TX_NOT_INTERRUPTABLE - /* Temporarily disable preemption. */ _tx_thread_preempt_disable++; - /* Restore interrupts. */ - TX_RESTORE -#endif /* Mutex is not owned, but it is possible that a thread that caused a priority inheritance to occur is no longer waiting @@ -374,22 +299,17 @@ UINT inheritance_priority; /* Setup the highest priority waiting thread. */ mutex_ptr -> tx_mutex_highest_priority_waiting = (UINT) TX_MAX_PRIORITIES; - /* Determine if we need to restore priority. */ - if ((mutex_ptr -> tx_mutex_owner) -> tx_thread_priority != old_priority) + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + _tx_mutex_priority_chain(mutex_ptr -> tx_mutex_owner); + } + else if ((mutex_ptr -> tx_mutex_owner) -> tx_thread_priority != old_priority) { - - /* Yes, restore the priority of thread. */ _tx_mutex_priority_change(mutex_ptr -> tx_mutex_owner, old_priority); } -#ifndef TX_NOT_INTERRUPTABLE - - /* Disable interrupts again. */ - TX_DISABLE - /* Back off the preemption disable. */ _tx_thread_preempt_disable--; -#endif /* Set the mutex owner to NULL. */ mutex_ptr -> tx_mutex_owner = TX_NULL; @@ -541,16 +461,11 @@ UINT inheritance_priority; } } - /* Restore previous priority needs to be restored after priority - inheritance. */ - - /* Determine if we need to restore priority. */ - if (old_owner -> tx_thread_priority != old_priority) - { - - /* Restore priority of thread. */ - _tx_mutex_priority_change(old_owner, old_priority); - } + _tx_thread_preempt_disable++; + /* Recompute both owners after ownership and wait-list changes. */ + _tx_mutex_priority_chain(old_owner); + _tx_mutex_priority_chain(thread_ptr); + _tx_thread_preempt_disable--; } /* Resume the thread! */ @@ -608,16 +523,11 @@ UINT inheritance_priority; TX_RESTORE } - /* Restore previous priority needs to be restored after priority - inheritance. */ - - /* Is the priority different? */ - if (old_owner -> tx_thread_priority != old_priority) - { - - /* Restore the priority of thread. */ - _tx_mutex_priority_change(old_owner, old_priority); - } + TX_DISABLE + /* Recompute both owners after ownership and wait-list changes. */ + _tx_mutex_priority_chain(old_owner); + _tx_mutex_priority_chain(thread_ptr); + TX_RESTORE } /* Resume thread. */ diff --git a/common/src/tx_thread_initialize.c b/common/src/tx_thread_initialize.c index c9cfea339..a6626ccd4 100644 --- a/common/src/tx_thread_initialize.c +++ b/common/src/tx_thread_initialize.c @@ -137,6 +137,8 @@ volatile UINT _tx_thread_preempt_disable; termination. This pointer is setup during mutex initialization. */ VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Optional mutex inheritance update, installed by mutex creation. */ +VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); /* Define the global build options variable. This contains a bit map representing @@ -347,6 +349,7 @@ VOID _tx_thread_initialize(VOID) /* Initialize the thread mutex release function pointer. */ _tx_thread_mutex_release = TX_NULL; + _tx_thread_mutex_priority_update = TX_NULL; #ifdef TX_ENABLE_STACK_CHECKING diff --git a/common/src/tx_thread_priority_change.c b/common/src/tx_thread_priority_change.c index bdf2a9930..221cd4994 100644 --- a/common/src/tx_thread_priority_change.c +++ b/common/src/tx_thread_priority_change.c @@ -28,6 +28,7 @@ #include "tx_api.h" #include "tx_trace.h" #include "tx_thread.h" +#include "tx_mutex.h" /**************************************************************************/ @@ -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; @@ -95,6 +97,33 @@ UINT original_priority; /* Log this kernel call. */ TX_EL_THREAD_PRIORITY_CHANGE_INSERT + /* A base-priority change on an active mutex wait changes a dependency. + Require an active suspension before interpreting its control block. */ + if ((thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) && + (thread_ptr -> tx_thread_suspend_cleanup != TX_NULL) && + (_tx_thread_mutex_priority_update != TX_NULL)) + { + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + if ((mutex_ptr != TX_NULL) && (mutex_ptr -> tx_mutex_id == TX_MUTEX_ID) && + (mutex_ptr -> tx_mutex_inherit == TX_TRUE)) + { + thread_ptr -> tx_thread_user_priority = new_priority; + thread_ptr -> tx_thread_user_preempt_threshold = new_priority; + if (thread_ptr -> tx_thread_inherit_priority < new_priority) + { + new_priority = thread_ptr -> tx_thread_inherit_priority; + } + thread_ptr -> tx_thread_priority = new_priority; + thread_ptr -> tx_thread_preempt_threshold = new_priority; + _tx_thread_preempt_disable++; + (_tx_thread_mutex_priority_update)(mutex_ptr); + _tx_thread_preempt_disable--; + TX_RESTORE + _tx_thread_system_preempt_check(); + return(TX_SUCCESS); + } + } + /* Determine if this thread is currently ready. */ if (thread_ptr -> tx_thread_state != TX_READY) { diff --git a/common_smp/inc/tx_mutex.h b/common_smp/inc/tx_mutex.h index 8f7ec84b3..218da41cf 100644 --- a/common_smp/inc/tx_mutex.h +++ b/common_smp/inc/tx_mutex.h @@ -81,9 +81,8 @@ 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_priority_chain(TX_THREAD *thread_ptr); +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr); 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_thread.h b/common_smp/inc/tx_thread.h index 1ffe88287..d7cfb4a61 100644 --- a/common_smp/inc/tx_thread.h +++ b/common_smp/inc/tx_thread.h @@ -9,8 +9,6 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Portions of this file were generated with AI assistance. - /**************************************************************************/ /**************************************************************************/ /** */ @@ -313,6 +311,8 @@ THREAD_DECLARE volatile UINT _tx_thread_preempt_disable; termination. This pointer is setup during mutex initialization. */ THREAD_DECLARE VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Optional mutex inheritance update, installed by mutex creation. */ +THREAD_DECLARE VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); /* Define the global build options variable. This contains a bit map representing diff --git a/common_smp/inc/tx_user_sample.h b/common_smp/inc/tx_user_sample.h index 0a68fb20e..814a3a0f2 100644 --- a/common_smp/inc/tx_user_sample.h +++ b/common_smp/inc/tx_user_sample.h @@ -9,8 +9,6 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -/* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ /** */ @@ -254,11 +252,22 @@ #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. */ +/* Enable bounded, bidirectional priority inheritance through mutex chains. + Define a nonnegative integer K to visit the first owner and at most K further + waiting-mutex edges (K + 1 distinct owners); zero retains a single owner. + Get, wait exit, waiter priority change, release and transfer use the same bound. + A non-inheriting mutex or inactive mutex wait ends the walk. Cycles wholly + contained in the visited path are recomputed from base priorities and outside + waiters, without retaining their own obsolete boosts. A cycle extending beyond + the bound can still retain an outside cached contribution; configure K to cover + the application's longest possible cycle if cycle restoration is required. + Work is O(N*N + N*W + M), for N <= K + 1 visited owners, W waiters and M owned + mutexes across those owners, plus up to N scheduler priority changes. All graph + processing holds interrupt/SMP protection with preemption disabled; the bound + alone does not bound list lengths or total interrupt latency. Stack scratch is + (K + 1)*(sizeof(TX_THREAD *) + 2*sizeof(UINT)), with no allocation or recursion. + When undefined, get retains its existing single-level path and restoration + scans only the changed waiter list and the first owner's cached mutex list. */ /* #define TX_MUTEX_INHERIT_CHAIN_MAX 8 diff --git a/common_smp/src/tx_mutex_cleanup.c b/common_smp/src/tx_mutex_cleanup.c index d32f69fc0..830fadcac 100644 --- a/common_smp/src/tx_mutex_cleanup.c +++ b/common_smp/src/tx_mutex_cleanup.c @@ -80,6 +80,11 @@ TX_THREAD *next_thread; TX_THREAD *previous_thread; +#ifdef TX_NOT_INTERRUPTABLE + /* Suspension sequences are not used in the non-interruptable kernel. */ + (VOID) suspension_sequence; +#endif + #ifndef TX_NOT_INTERRUPTABLE /* Disable interrupts to remove the suspended thread from the mutex. */ @@ -155,6 +160,14 @@ TX_THREAD *previous_thread; } } + /* Remove this waiter's inheritance before it can resume. */ + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + _tx_thread_preempt_disable++; + _tx_mutex_inherit_priority_update(mutex_ptr); + _tx_thread_preempt_disable--; + } + /* Now we need to determine if this cleanup is from a terminate, timeout, or from a wait abort. */ if (thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) diff --git a/common_smp/src/tx_mutex_create.c b/common_smp/src/tx_mutex_create.c index a1f5eba85..497a14b77 100644 --- a/common_smp/src/tx_mutex_create.c +++ b/common_smp/src/tx_mutex_create.c @@ -9,8 +9,6 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -/* Portions of this file were generated with AI assistance. */ - /**************************************************************************/ /**************************************************************************/ @@ -91,6 +89,7 @@ TX_MUTEX *previous_mutex; /* Setup the thread mutex release function pointer. */ _tx_thread_mutex_release = &(_tx_mutex_thread_release); + _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. */ diff --git a/common_smp/src/tx_mutex_get.c b/common_smp/src/tx_mutex_get.c index ee32006ba..86a648041 100644 --- a/common_smp/src/tx_mutex_get.c +++ b/common_smp/src/tx_mutex_get.c @@ -60,7 +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 */ +/* _tx_mutex_inherit_priority_update Recompute mutex inheritance */ /* */ /* CALLED BY */ /* */ @@ -75,7 +75,9 @@ TX_INTERRUPT_SAVE_AREA TX_THREAD *thread_ptr; TX_MUTEX *next_mutex; TX_MUTEX *previous_mutex; +#if !defined(TX_MUTEX_INHERIT_CHAIN_MAX) || defined(TX_MUTEX_ENABLE_PERFORMANCE_INFO) TX_THREAD *mutex_owner; +#endif TX_THREAD *next_thread; TX_THREAD *previous_thread; UINT status; @@ -204,7 +206,9 @@ UINT status; /* Prepare for suspension of this thread. */ /* Pickup the mutex owner. */ +#if !defined(TX_MUTEX_INHERIT_CHAIN_MAX) || defined(TX_MUTEX_ENABLE_PERFORMANCE_INFO) mutex_owner = mutex_ptr -> tx_mutex_owner; +#endif #ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO @@ -285,6 +289,18 @@ UINT status; owning the mutex. */ if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) { +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +#ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO + if (mutex_owner -> tx_thread_priority > thread_ptr -> tx_thread_priority) + { + _tx_mutex_performance__priority_inheritance_count++; + mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; + } +#endif + _tx_thread_preempt_disable++; + _tx_mutex_inherit_priority_update(mutex_ptr); + _tx_thread_preempt_disable--; +#else /* Determine if this is the highest priority to raise for this mutex. */ if (mutex_ptr -> tx_mutex_highest_priority_waiting > thread_ptr -> tx_thread_priority) @@ -310,12 +326,6 @@ 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. */ @@ -325,6 +335,7 @@ UINT status; mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; #endif } +#endif } /* Call actual non-interruptable thread suspension routine. */ @@ -343,13 +354,25 @@ UINT status; /* Temporarily disable preemption. */ _tx_thread_preempt_disable++; +#ifndef TX_MUTEX_INHERIT_CHAIN_MAX /* Restore interrupts. */ TX_RESTORE +#endif /* Determine if we need to raise the priority of the thread owning the mutex. */ if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) { +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +#ifdef TX_MUTEX_ENABLE_PERFORMANCE_INFO + if (mutex_owner -> tx_thread_priority > thread_ptr -> tx_thread_priority) + { + _tx_mutex_performance__priority_inheritance_count++; + mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; + } +#endif + _tx_mutex_inherit_priority_update(mutex_ptr); +#else /* Determine if this is the highest priority to raise for this mutex. */ if (mutex_ptr -> tx_mutex_highest_priority_waiting > thread_ptr -> tx_thread_priority) @@ -375,12 +398,6 @@ 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. */ @@ -390,8 +407,13 @@ UINT status; mutex_ptr -> tx_mutex_performance__priority_inheritance_count++; #endif } +#endif } +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX + TX_RESTORE +#endif + /* Call actual thread suspension routine. */ _tx_thread_system_suspend(thread_ptr); #endif diff --git a/common_smp/src/tx_mutex_priority_change.c b/common_smp/src/tx_mutex_priority_change.c index f33d9e081..457deb1bf 100644 --- a/common_smp/src/tx_mutex_priority_change.c +++ b/common_smp/src/tx_mutex_priority_change.c @@ -485,123 +485,267 @@ UINT finished; } -#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_inherit_priority_update */ /* */ -/* _tx_mutex_get Inherit priority */ +/* Recompute an inheriting mutex after a waiter changes or leaves. */ +/* Caller holds interrupt/SMP protection and disables preemption. */ +/* No list reordering is needed: put prioritizes before transfer. */ /* */ /**************************************************************************/ -VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr, UINT new_priority) +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr) { -#ifndef TX_NOT_INTERRUPTABLE -TX_INTERRUPT_SAVE_AREA -#endif - -TX_MUTEX *mutex_ptr; -TX_THREAD *owner_ptr; -UINT chain_length; +#ifndef TX_MUTEX_INHERIT_CHAIN_MAX +TX_THREAD *waiter; +UINT remaining; +UINT priority; - /* 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))) + priority = ((UINT) TX_MAX_PRIORITIES); + waiter = mutex_ptr -> tx_mutex_suspension_list; + remaining = mutex_ptr -> tx_mutex_suspended_count; + while (remaining != ((UINT) 0)) { + if (waiter -> tx_thread_priority < priority) + { + priority = waiter -> tx_thread_priority; + } + waiter = waiter -> tx_thread_suspended_next; + remaining--; + } + mutex_ptr -> tx_mutex_highest_priority_waiting = priority; - /* 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)) - { + if (mutex_ptr -> tx_mutex_owner != TX_NULL) + { + _tx_mutex_priority_chain(mutex_ptr -> tx_mutex_owner); + } +} - /* 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) +/**************************************************************************/ +/* */ +/* _tx_mutex_priority_chain */ +/* */ +/* Rebuild inheritance, including decreases, before publishing it. */ +/* With chaining disabled this is the ordinary owned-mutex cache scan. */ +/* Otherwise collect the first owner and at most CHAIN_MAX further */ +/* edges, stopping on a repeated owner or an invalid wait context. */ +/* */ +/* Within the collected path, effective priorities are results, never */ +/* seeds. Seeds are user priorities and waiters outside the path. */ +/* Relaxing its internal edges from these seeds removes stale boosts */ +/* even in a cycle wholly contained in the path. Cycles extending */ +/* beyond the configured bound can still contain stale outside seeds; */ +/* the bound does not promise restoration outside this window. */ +/* */ +/* Caller holds interrupt/SMP protection and disables preemption. */ +/* Scheduler priority changes retain that protection, including in NI. */ +/* Work also depends on owned-mutex and waiter counts (see tx_user.h). */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_priority_chain(TX_THREAD *thread_ptr) +{ + +TX_MUTEX *mutex_ptr; +UINT priority; +UINT inheritance; +#ifdef TX_MUTEX_INHERIT_CHAIN_MAX +TX_THREAD *chain[TX_MUTEX_INHERIT_CHAIN_MAX + 1]; +UINT effective[TX_MUTEX_INHERIT_CHAIN_MAX + 1]; +UINT inherited[TX_MUTEX_INHERIT_CHAIN_MAX + 1]; +TX_THREAD *owner; +TX_THREAD *waiter; +UINT count; +UINT cycle; +UINT i; +UINT j; +UINT pass; +UINT target; +UINT remaining; + + + count = ((UINT) 1); + chain[0] = thread_ptr; + cycle = ((UINT) TX_MUTEX_INHERIT_CHAIN_MAX) + ((UINT) 1); + for (;;) + { + thread_ptr = chain[count - ((UINT) 1)]; + if ((thread_ptr -> tx_thread_state != TX_MUTEX_SUSP) || + (thread_ptr -> tx_thread_suspend_cleanup != &(_tx_mutex_cleanup))) + { + break; + } + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + if ((mutex_ptr == TX_NULL) || (mutex_ptr -> tx_mutex_id != TX_MUTEX_ID) || + (mutex_ptr -> tx_mutex_inherit != TX_TRUE) || + (mutex_ptr -> tx_mutex_ownership_count == ((UINT) 0)) || + (mutex_ptr -> tx_mutex_owner == TX_NULL)) + { + break; + } + owner = mutex_ptr -> tx_mutex_owner; + for (j = 0; j < count; j++) + { + if (chain[j] == owner) { + break; + } + } + if (j < count) + { + cycle = j; + break; + } + if (count == (((UINT) TX_MUTEX_INHERIT_CHAIN_MAX) + ((UINT) 1))) + { + break; + } + chain[count] = owner; + count++; + } - /* Determine if this is the highest priority to raise for this mutex. */ - if (mutex_ptr -> tx_mutex_highest_priority_waiting > new_priority) + /* Seed only from contributions that do not depend on this path. */ + for (i = 0; i < count; i++) + { + inherited[i] = ((UINT) TX_MAX_PRIORITIES); + mutex_ptr = chain[i] -> tx_thread_owned_mutex_list; + if (mutex_ptr != TX_NULL) + { + do + { + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) { - - /* Remember this priority. */ - mutex_ptr -> tx_mutex_highest_priority_waiting = new_priority; + waiter = mutex_ptr -> tx_mutex_suspension_list; + remaining = mutex_ptr -> tx_mutex_suspended_count; + while (remaining != ((UINT) 0)) + { + for (j = 0; j < count; j++) + { + if (chain[j] == waiter) + { + break; + } + } + if ((j == count) && (waiter -> tx_thread_priority < inherited[i])) + { + inherited[i] = waiter -> tx_thread_priority; + } + waiter = waiter -> tx_thread_suspended_next; + remaining--; + } } + mutex_ptr = mutex_ptr -> tx_mutex_owned_next; + } while (mutex_ptr != chain[i] -> tx_thread_owned_mutex_list); + } + effective[i] = chain[i] -> tx_thread_user_priority; + if (inherited[i] < effective[i]) + { + effective[i] = inherited[i]; + } + } - /* 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) + /* Forward order propagates every seed along the tail on the first pass. + A back edge can then move the minimum to an earlier cycle member; + the second pass carries it around the entire cycle. */ + for (pass = 0; pass < ((UINT) 2); pass++) + { + for (i = 0; i < count; i++) + { + target = i + ((UINT) 1); + if (target == count) + { + target = cycle; + } + if (target < count) + { + if (effective[i] < inherited[target]) { - - /* Remember the new priority inheritance priority. */ - owner_ptr -> tx_thread_inherit_priority = new_priority; + inherited[target] = effective[i]; } - - /* Determine if the owner already runs at the priority or higher. */ - if (owner_ptr -> tx_thread_priority <= new_priority) + if (effective[i] < effective[target]) { - - /* Yes, the chain ends here. */ - owner_ptr = TX_NULL; + effective[target] = effective[i]; } } } + } -#ifndef TX_NOT_INTERRUPTABLE - - /* Restore interrupts. */ - TX_RESTORE -#endif - - /* Determine if there is an owner to raise. */ - if (owner_ptr != TX_NULL) + /* Refresh every affected mutex cache from the solved priorities. */ + for (i = 0; i < count; i++) + { + mutex_ptr = chain[i] -> tx_thread_owned_mutex_list; + if (mutex_ptr != TX_NULL) { - - /* Raise the owner's priority to the inherited priority. */ - _tx_mutex_priority_change(owner_ptr, new_priority); + do + { + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + priority = ((UINT) TX_MAX_PRIORITIES); + waiter = mutex_ptr -> tx_mutex_suspension_list; + remaining = mutex_ptr -> tx_mutex_suspended_count; + while (remaining != ((UINT) 0)) + { + inheritance = waiter -> tx_thread_priority; + for (j = 0; j < count; j++) + { + if (chain[j] == waiter) + { + inheritance = effective[j]; + break; + } + } + if (inheritance < priority) + { + priority = inheritance; + } + waiter = waiter -> tx_thread_suspended_next; + remaining--; + } + mutex_ptr -> tx_mutex_highest_priority_waiting = priority; + } + mutex_ptr = mutex_ptr -> tx_mutex_owned_next; + } while (mutex_ptr != chain[i] -> tx_thread_owned_mutex_list); + } + chain[i] -> tx_thread_inherit_priority = inherited[i]; + } + for (i = 0; i < count; i++) + { + if (chain[i] -> tx_thread_priority != effective[i]) + { + _tx_mutex_priority_change(chain[i], effective[i]); } + } +#else - /* Move to the next thread in the chain. */ - thread_ptr = owner_ptr; - chain_length++; + /* Preserve the single-level owned-cache scan when chaining is off. */ + inheritance = ((UINT) TX_MAX_PRIORITIES); + mutex_ptr = thread_ptr -> tx_thread_owned_mutex_list; + if (mutex_ptr != TX_NULL) + { + do + { + if ((mutex_ptr -> tx_mutex_inherit == TX_TRUE) && + (mutex_ptr -> tx_mutex_highest_priority_waiting < inheritance)) + { + inheritance = mutex_ptr -> tx_mutex_highest_priority_waiting; + } + mutex_ptr = mutex_ptr -> tx_mutex_owned_next; + } while (mutex_ptr != thread_ptr -> tx_thread_owned_mutex_list); + } + thread_ptr -> tx_thread_inherit_priority = inheritance; + priority = thread_ptr -> tx_thread_user_priority; + if (inheritance < priority) + { + priority = inheritance; + } + if (thread_ptr -> tx_thread_priority != priority) + { + _tx_mutex_priority_change(thread_ptr, priority); } -} #endif +} diff --git a/common_smp/src/tx_mutex_put.c b/common_smp/src/tx_mutex_put.c index 062472a24..453bba06a 100644 --- a/common_smp/src/tx_mutex_put.c +++ b/common_smp/src/tx_mutex_put.c @@ -86,7 +86,6 @@ TX_THREAD *current_thread; TX_THREAD *next_thread; TX_THREAD *previous_thread; TX_THREAD *suspended_thread; -UINT inheritance_priority; /* Setup status to indicate the processing is not complete. */ @@ -234,75 +233,6 @@ UINT inheritance_priority; old_owner = TX_NULL; old_priority = thread_ptr -> tx_thread_user_priority; - /* Does this mutex support priority inheritance? */ - if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) - { - -#ifndef TX_NOT_INTERRUPTABLE - - /* Temporarily disable preemption. */ - _tx_thread_preempt_disable++; - - /* Restore interrupts. */ - TX_RESTORE -#endif - - /* Default the inheritance priority to disabled. */ - inheritance_priority = ((UINT) TX_MAX_PRIORITIES); - - /* Search the owned mutexes for this thread to determine the highest priority for this - former mutex owner to return to. */ - next_mutex = thread_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 priority to - restore. */ - if (next_mutex -> tx_mutex_highest_priority_waiting < inheritance_priority) - { - - /* Use this priority to return releasing thread to. */ - 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 == thread_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 - - /* Undo the temporarily preemption disable. */ - _tx_thread_preempt_disable--; -#endif - - /* Set the inherit priority to that of the highest priority thread waiting on the mutex. */ - thread_ptr -> tx_thread_inherit_priority = inheritance_priority; - - /* Determine if the inheritance priority is less than the default old priority. */ - if (inheritance_priority < old_priority) - { - - /* Yes, update the old priority. */ - old_priority = inheritance_priority; - } - } - /* Determine if priority inheritance is in effect and there are one or more threads suspended on the mutex. */ if (mutex_ptr -> tx_mutex_suspended_count > ((UINT) 1)) @@ -358,14 +288,9 @@ UINT inheritance_priority; /* No, there are no longer any threads waiting on the mutex. */ -#ifndef TX_NOT_INTERRUPTABLE - /* Temporarily disable preemption. */ _tx_thread_preempt_disable++; - /* Restore interrupts. */ - TX_RESTORE -#endif /* Mutex is not owned, but it is possible that a thread that caused a priority inheritance to occur is no longer waiting @@ -374,22 +299,17 @@ UINT inheritance_priority; /* Setup the highest priority waiting thread. */ mutex_ptr -> tx_mutex_highest_priority_waiting = (UINT) TX_MAX_PRIORITIES; - /* Determine if we need to restore priority. */ - if ((mutex_ptr -> tx_mutex_owner) -> tx_thread_priority != old_priority) + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + _tx_mutex_priority_chain(mutex_ptr -> tx_mutex_owner); + } + else if ((mutex_ptr -> tx_mutex_owner) -> tx_thread_priority != old_priority) { - - /* Yes, restore the priority of thread. */ _tx_mutex_priority_change(mutex_ptr -> tx_mutex_owner, old_priority); } -#ifndef TX_NOT_INTERRUPTABLE - - /* Disable interrupts again. */ - TX_DISABLE - /* Back off the preemption disable. */ _tx_thread_preempt_disable--; -#endif /* Set the mutex owner to NULL. */ mutex_ptr -> tx_mutex_owner = TX_NULL; @@ -541,16 +461,11 @@ UINT inheritance_priority; } } - /* Restore previous priority needs to be restored after priority - inheritance. */ - - /* Determine if we need to restore priority. */ - if (old_owner -> tx_thread_priority != old_priority) - { - - /* Restore priority of thread. */ - _tx_mutex_priority_change(old_owner, old_priority); - } + _tx_thread_preempt_disable++; + /* Recompute both owners after ownership and wait-list changes. */ + _tx_mutex_priority_chain(old_owner); + _tx_mutex_priority_chain(thread_ptr); + _tx_thread_preempt_disable--; } /* Resume the thread! */ @@ -608,16 +523,11 @@ UINT inheritance_priority; TX_RESTORE } - /* Restore previous priority needs to be restored after priority - inheritance. */ - - /* Is the priority different? */ - if (old_owner -> tx_thread_priority != old_priority) - { - - /* Restore the priority of thread. */ - _tx_mutex_priority_change(old_owner, old_priority); - } + TX_DISABLE + /* Recompute both owners after ownership and wait-list changes. */ + _tx_mutex_priority_chain(old_owner); + _tx_mutex_priority_chain(thread_ptr); + TX_RESTORE } /* Resume thread. */ diff --git a/common_smp/src/tx_thread_initialize.c b/common_smp/src/tx_thread_initialize.c index f37931096..fffa0ca0f 100644 --- a/common_smp/src/tx_thread_initialize.c +++ b/common_smp/src/tx_thread_initialize.c @@ -176,6 +176,8 @@ volatile UINT _tx_thread_preempt_disable; termination. This pointer is setup during mutex initialization. */ VOID (*_tx_thread_mutex_release)(TX_THREAD *thread_ptr); +/* Optional mutex inheritance update, installed by mutex creation. */ +VOID (*_tx_thread_mutex_priority_update)(TX_MUTEX *mutex_ptr); /* Define the global build options variable. This contains a bit map representing @@ -385,6 +387,7 @@ VOID _tx_thread_initialize(VOID) /* Initialize the thread mutex release function pointer. */ _tx_thread_mutex_release = TX_NULL; + _tx_thread_mutex_priority_update = TX_NULL; #ifdef TX_ENABLE_STACK_CHECKING diff --git a/common_smp/src/tx_thread_priority_change.c b/common_smp/src/tx_thread_priority_change.c index 57d6eda79..db454ef1b 100644 --- a/common_smp/src/tx_thread_priority_change.c +++ b/common_smp/src/tx_thread_priority_change.c @@ -29,6 +29,7 @@ #include "tx_api.h" #include "tx_trace.h" #include "tx_thread.h" +#include "tx_mutex.h" /**************************************************************************/ @@ -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; @@ -112,6 +114,33 @@ UINT status; /* Save the previous priority. */ *old_priority = thread_ptr -> tx_thread_user_priority; + /* A base-priority change on an active mutex wait changes a dependency. + Require an active suspension before interpreting its control block. */ + if ((thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) && + (thread_ptr -> tx_thread_suspend_cleanup != TX_NULL) && + (_tx_thread_mutex_priority_update != TX_NULL)) + { + mutex_ptr = TX_VOID_TO_MUTEX_POINTER_CONVERT(thread_ptr -> tx_thread_suspend_control_block); + if ((mutex_ptr != TX_NULL) && (mutex_ptr -> tx_mutex_id == TX_MUTEX_ID) && + (mutex_ptr -> tx_mutex_inherit == TX_TRUE)) + { + thread_ptr -> tx_thread_user_priority = new_priority; + thread_ptr -> tx_thread_user_preempt_threshold = new_priority; + if (thread_ptr -> tx_thread_inherit_priority < new_priority) + { + new_priority = thread_ptr -> tx_thread_inherit_priority; + } + thread_ptr -> tx_thread_priority = new_priority; + thread_ptr -> tx_thread_preempt_threshold = new_priority; + _tx_thread_preempt_disable++; + (_tx_thread_mutex_priority_update)(mutex_ptr); + _tx_thread_preempt_disable--; + TX_RESTORE + _tx_thread_system_preempt_check(); + return(TX_SUCCESS); + } + } + /* Determine if the new priority is the same as the last requested priority. */ if ((thread_ptr -> tx_thread_user_priority == new_priority) && (thread_ptr -> tx_thread_user_preempt_threshold == new_priority)) @@ -128,10 +157,12 @@ UINT status; /* Determine if the inherit priority is in effect and there is no preemption-threshold in force. */ else if ((thread_ptr -> tx_thread_inherit_priority < new_priority) && + (thread_ptr -> tx_thread_priority == thread_ptr -> tx_thread_inherit_priority) && (thread_ptr -> tx_thread_user_preempt_threshold == thread_ptr -> tx_thread_user_priority)) { - /* In this case, simply setup the user priority and preemption-threshold and return. */ + /* The effective priority is already the inherited priority. If it were + still the old user priority, it would need to change as well. */ /* Setup the new priority for this thread. */ thread_ptr -> tx_thread_user_priority = new_priority; diff --git a/docs/mutex-inherit-chain-restoration.adoc b/docs/mutex-inherit-chain-restoration.adoc new file mode 100644 index 000000000..d4f7a2fda --- /dev/null +++ b/docs/mutex-inherit-chain-restoration.adoc @@ -0,0 +1,369 @@ += Mutex inheritance chain restoration: implementation and validation + +== Checkout and scope + +Investigated `fix/mutex-inherit-chain`, HEAD `2820d6d61ce16d7073c0232163b3b8875694b573`. +The original worktree contained only untracked `ISSUES.md`; that file is preserved. +No commits, staging, fetches, remote changes, pushes, merges, or GitHub changes were made. +The supplied review scenario was used without accessing the network. + +The checkout contains #808's boost-only chain implementation, but not #807. +Local history also contains #807 (`d2db0b3f`) and the waiter-priority branch +(`09c8547a`, `44fcf703`), which were inspected, not merged or cherry-picked. +Only necessary compatible restoration behavior, the direct-restoration tests, +and the native optional mutex callback pattern were reused. + +== Root cause and invariants + +`_tx_mutex_get` recorded a waiter's effective priority in the mutex and owner, +then `_tx_mutex_priority_chain` copied that number downstream. Its comparison +against an already equal/higher-priority owner both stopped boosts and broke cycles. +It had no withdrawal operation. `_tx_mutex_cleanup` only unlinked waiters. +With #807 alone, cleanup restores the first owner but does not refresh that owner's +outgoing mutex contribution or the next owner's effective priority. + +The relevant state has distinct responsibilities: + +[cols="1,3",options="header"] +|=== +|Field |Meaning and update responsibility +|`tx_thread_user_priority` |Configured priority; thread-priority API owns changes. This is an independent recomputation seed. +|`tx_thread_priority` |Effective scheduling priority; the existing scheduler-specific priority functions maintain ready lists, execution selection, and thresholds. Within a visited cycle it is a result, not an independent seed. +|`tx_thread_inherit_priority` |Cached minimum donation across owned inheriting mutexes; `TX_MAX_PRIORITIES` means none. It must not seed its own cycle. +|`tx_mutex_highest_priority_waiting` |Cached minimum effective priority of waiters; it must be refreshed when membership or a waiter's priority changes. +|`tx_thread_owned_mutex_list` |Circular ownership list, including non-inheriting mutexes. Get/put own its links. Only inheriting mutexes contribute. +|Suspension list and count |Actual incoming dependency edges. Get, cleanup, put, and delete own membership. Recalculation never reorders or unlinks them. +|`tx_thread_state`, `tx_thread_suspend_cleanup`, `tx_thread_suspend_control_block` |Outgoing wait is usable only in an active mutex suspension. Chain discovery checks `TX_MUTEX_SUSP`, the exact `_tx_mutex_cleanup` function, non-null/valid mutex, inheritance, ownership count, and owner. Cleanup clears its marker before recalculation; transfer clears it before resuming the recipient. +|`tx_mutex_original_priority` |Acquisition-time bookkeeping, not a current priority contribution or restoration seed. +|=== + +The transfer regression exposed another SMP defect: `_tx_thread_priority_change`'s +inheritance fast path updated only user fields even when the effective priority +was still the old user priority. A recipient at base 5 with a remaining priority-10 +waiter stayed at 5 after its base became 18. The fast path now additionally requires +that effective priority already equals inherited priority. Both suspended and +remotely executing ready owners exercise this correction. + +== Recalculation and synchronization + +`_tx_mutex_inherit_priority_update` is the common mutex entry point. +`_tx_mutex_priority_chain` owns recomputation for an owner, including release. +When chaining is disabled, the former scans the changed wait list and the latter +performs the original owned-mutex cache scan. The get path remains single-level. +No waiter-list prioritization is needed during recalculation; put retains its +existing prioritization before selecting a recipient. + +With chaining enabled, the updater: + +. Collects the first owner and at most K further waiting-mutex edges, stopping at an inactive/non-inheriting wait or repeated owner. +. Seeds scratch priorities from user priorities and waiters outside the collected path, ignoring effective-priority caches of all path members. +. Relaxes internal path edges in forward order twice. The first pass propagates tail seeds and can carry a minimum across a back edge; the second carries that minimum through the cycle. There is no recursive propagation or convergence retry loop. +. Refreshes all inheriting mutex caches and inherited-priority fields in the path from the solved values, then publishes effective changes once per owner through `_tx_mutex_priority_change`. + +All observations and publication hold `TX_DISABLE` protection and preemption +disable. In SMP, this is the port's shared kernel protection, not just local +interrupt masking. Nested scheduler operations retain the outer protection; +no graph pointer is retained across an unlocked graph transaction. The original +single-core ready-list/threshold code and SMP ready-list, core mapping, rebalance, +and remote preemption code are retained. NI callers supply the same preemption +contract, and cleanup marks its unused NI suspension sequence explicitly. + +Wait abort changes the departing thread's state before cleanup; termination also +sets a terminal state and prevents deletion during cleanup. Neither path is used +as an outgoing wait after detachment. Timeout restoration occurs before resume. +New ownership is linked and the recipient's cleanup marker cleared before either +owner is recomputed. Delete and termination release owned mutexes through put. + +The optional `_tx_thread_mutex_priority_update` callback follows the existing +`_tx_thread_mutex_release` pattern: thread-only applications do not acquire a direct +link dependency on mutex services. Creation installs it and thread initialization +clears it. Thread-priority changes on active inheriting waits call it while protected, +including base changes inside a cycle whose initially cached effective priority +appears unchanged. Non-inheriting waits retain their FIFO behavior. + +== Event and regression mapping + +Both regression trees contain the same chain and wait-exit tests. The wait-exit +suite is adopted from local #807; no effective priorities are assigned manually. +The RISC-V manifest now registers both suites as well. + +[cols="1,3",options="header"] +|=== +|Requirement/event |Test or execution path +|T1/T2/T3: timeout, abort, terminate |`restore_chain(2, how)` and direct `single_waiter_leaves`; assert restored priorities, status, empty A list, intact B ring, and ownership count. +|T4: three owners |`restore_chain(3, how)` for limits at least 2, through all three wait-exit routes. +|T5/T6: remaining donations |`remaining_contributions` keeps a priority-10 waiter on A or on a second mutex owned by L1, withdraws priority 5/4, then removes the remaining contribution. +|T7: stronger/weaker configured priority |Actively waiting H changes 5 to 12 to 4; retained priority-10 contribution wins where appropriate. A contained cycle member changes base 15 to 25. Transferred H changes base 5 to 18 with remaining donations 10 and 15. +|T8: release/transfer |`ownership_transfer` hands A to H with two remaining waiters, verifies H's inherited cache, and terminates those waiters in turn. `delete_while_waiting` releases A through kernel deletion while L1 remains blocked on B, checking restoration downstream. +|T9: ready owner/scheduling |`chain_to_ready_owner` keeps L2 ready, checks 5 to 20 to 30 restoration and that a runnable priority-10 thread executes. It also changes L2's user priority across its inherited priority. SMP explicitly excludes L2 from core 0, exercising a remotely executing owner. +|T10: cycles |`chain_with_cycle` forms an actual two-owner deadlock, adds/removes H, checks both priorities and wait-list rings, then weakens a cycle member's base. Both owners remain mutex-suspended. +|T11: bounds |Four-owner decreasing-base chain checks both boost and restoration boundaries. With K=2, two-owner, three-owner, and four-owner cases are shorter than, equal to, and longer than the two further-edge bound. K=0 and K=1 are also executed. +|T12: disabled |Same regressions expect only direct inheritance; independent direct-restoration and waiter-priority fixes remain enabled. +|T13/T14 |Single-core QEMU and adapted SMP simulator; chain on/off, NI, disabled preemption thresholds, MISRA, and performance information as detailed below. +|Non-inheriting edge |`non_inheriting_edge` stops propagation at B. The priority API only invokes inheritance updates for inheriting waits. +|Other suspended/ready thread priority API |Native scheduler paths suffice if no outgoing mutex dependency exists; no unrelated hooks were added. +|Ownership/wait changes |Get, cleanup, priority API, and put are the required hooks. Delete and thread termination already funnel ownership release through put; wait abort and timeout already funnel removal through cleanup. +|=== + +== Source changes + +Every production file modified is listed below; corresponding common and SMP +files share semantic logic but retain their existing scheduler implementation. + +[cols="2,2,3",options="header"] +|=== +|Single-core file |SMP file |Change +|`common/src/tx_mutex_cleanup.c` |`common_smp/src/tx_mutex_cleanup.c` |Recompute after unlink, before resume; NI parameter annotation. +|`common/src/tx_mutex_create.c` |`common_smp/src/tx_mutex_create.c` |Install optional inheritance-update callback. +|`common/src/tx_mutex_get.c` |`common_smp/src/tx_mutex_get.c` |Use protected recomputation for enabled chains, including unchanged first-owner priorities; preserve performance counters and disabled get path. +|`common/src/tx_mutex_priority_change.c` |`common_smp/src/tx_mutex_priority_change.c` |Replace boost-only walk with shared recomputation and bounded scratch path solver. Existing scheduler-specific priority changer is retained. +|`common/src/tx_mutex_put.c` |`common_smp/src/tx_mutex_put.c` |Reuse updater instead of a duplicate owned-mutex scan; recompute former and transferred owners after relationship changes. +|`common/src/tx_thread_priority_change.c` |`common_smp/src/tx_thread_priority_change.c` |Propagate active inheriting-wait base changes. SMP also guards its unchanged-effective-priority optimization correctly. +|`common/src/tx_thread_initialize.c` |`common_smp/src/tx_thread_initialize.c` |Define/initialize optional callback. +|`common/inc/tx_mutex.h` |`common_smp/inc/tx_mutex.h` |Internal updater declarations. +|`common/inc/tx_thread.h` |`common_smp/inc/tx_thread.h` |Optional callback declaration; no control-block fields added. +|`common/inc/tx_user_sample.h` |`common_smp/inc/tx_user_sample.h` |Document edge count, bidirectional behavior, cycles, list-dependent cost, and scratch stack. +|=== + +The test manifests and batch testcontrol registries register the new direct-restoration +suite; existing chain tests cover the additional events. +== Reproduction and validation evidence + +The baseline scenario uses real mutex calls: H has priority 5; L1 owns A at base +20 and waits on B; L2 owns B at base 30 and remains ready. + +[source,text] +---- +Original HEAD, K=2: +After H abort: L1=5 L2=5 (expected 20,20) +ERROR #25 + +Only #807 cleanup/helper temporarily applied: +After H abort: L1=20 L2=5 (expected 20,20) +ERROR #26 + +Repair: +After H abort: L1=20 L2=20 (expected 20,20) +SUCCESS! +---- + +The #807 overlay was removed before implementing the repair. Logs: +`/tmp/threadx-808/baseline-test.log`, `807-test.log`, and final regression logs. +The expanded transfer test also failed with `ERROR #281` on SMP before the +fast-path correction (`smp-transfer-before.log`), while passing on single-core. + +Supported single-core runner used RISC-V64 GCC 13.2 and QEMU virt. The final full +suite passes 99/99 tests with K=2. All 12 mutex tests pass with K=2 and with +chaining undefined. Both new regressions additionally pass with K=0, K=1, +K=2+NI, undefined+NI, K=2+disabled preemption thresholds, and K=2+mutex/thread +performance information. + +This macOS ARM host cannot execute the repository's 32-bit Linux runner directly; +Docker is unavailable. Supplemental native runs use an isolated source copy under +`/tmp/threadx-808/host-tree`, the repository's CMake/CTest suites and Linux simulation +scheduler, with temporary adapters for named POSIX semaphores, missing timed waits, +and single-core simulator recursive-mutex counts. Host builds remove `-m32` and +`-Wconversion` from the broad suite's flags, and link gcov into executables. +No host adapter or replacement of the native scheduler algorithms is included in the patch. +These are adapted simulator results, not validation on a supported Linux host or +SMP hardware. The SMP simulator uses four virtual cores; its default tests pin +threads to core 0 unless explicitly excluded, as the remote-owner case does. + +In both adapted kernels, all 12 mutex tests pass with chaining on/off, and both +new regressions pass with K=2+NI, K=2+MISRA, and K=2+disabled preemption thresholds. +For NI builds, existing unused-parameter diagnostics outside the edited cleanup +are suppressed. RV64 MISRA cannot link from a user-header-only definition because +the shim tests its macro before including that header; defining it on the compiler +command line then exposes existing 32-bit ULONG/64-bit pointer errors in the shim. +RV32 attempts could not link with the installed multilib selection. MISRA runtime +evidence therefore comes from the adapted simulators, not QEMU RV64/RV32. + +A broad adapted SMP run passed 115/120: three random scheduling tests exceeded the +exploratory 15-second limit, the stack-fill test failed, and the trace test did not +build because its platform check selected Windows-only code. The same three +timeouts and stack-fill failure reproduced against unchanged HEAD production code +in a separate adapted tree. These baseline limitations were not hidden or patched. +One adapted single-core disabled-chain nested-inheritance test also timed out once; +it passed the next five consecutive executions. Its transient cause is unverified. +The supported QEMU suite has no corresponding failure. + +Strict standalone compilation checks all seven edited C files per kernel in six +configurations: undefined, K=2, K=2+NI, K=2+MISRA, K=2+disabled thresholds, and +K=2+NI+MISRA+disabled thresholds+mutex performance. All 84 compilations pass with +C99, `-O2 -Wall -Wextra -Werror -Wconversion -Wmissing-declarations +-Wpointer-arith -Wlogical-op -Waggregate-return -Wfloat-equal`. +Single-core additionally uses `-pedantic`; SMP omits it because of an existing +Linux-port duplicate TX_THREAD typedef under C99. Temporary host builds emit +existing toolchain deployment-version warnings/debug-linker notes; strict source +compilations introduce no warnings. + +Reproducible local commands and complete command logs are retained under +`/tmp/threadx-808`: + +[source,sh] +---- +cmake -S test/tx/cmake/riscv -B /tmp/threadx-808/baseline -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE="$PWD/cmake/riscv64_gnu.cmake" \ + -DTX_USER_FILE=/tmp/threadx-808/tx_user.h +cmake --build /tmp/threadx-808/baseline +ctest --test-dir /tmp/threadx-808/baseline --output-on-failure -j 4 +python3 /tmp/threadx-808/final-verify.py +python3 /tmp/threadx-808/strict.py +python3 /tmp/threadx-808/collect-coverage.py +---- + +`tx_user.h` is the sample user header followed by `#define TX_MUTEX_INHERIT_CHAIN_MAX 2`. +The matrix scripts record exact commands/configuration files, including NI diagnostic +suppression and compiler-command-line MISRA definitions. `host-setup.py`, +`host-compat.h`, and subsequent host port adaptations are temporary environment aids. +Coverage and simulator artifacts are local evidence, not shipped port changes. + +== Coverage + +Coverage was collected from actual native simulator execution using GCC/gcov 12.5, +`-fprofile-arcs -ftest-coverage`, and `gcov-12 -b -c -f -j` on each production +`.gcno`. Branch coverage means outcomes taken at least once, not merely decisions +reached. Reports union executable source lines and source-line branch indices across +five configurations per kernel: K=2 default, undefined+disabled notify callbacks, +K=2+NI, K=2+MISRA, and K=2+disabled thresholds. Default/disabled builds run all mutex +regressions and the existing thread-priority-change regression; alternative builds +run the two inheritance regressions. + +[cols="3,1,1,1",options="header"] +|=== +|Production source |Lines |Branches |Changed executable lines +|`common/src/tx_mutex_cleanup.c` |100.00% (47/47) |95.83% (23/24) |100.00% (4/4) +|`common/src/tx_mutex_create.c` |100.00% (20/20) |100.00% (4/4) |100.00% (1/1) +|`common/src/tx_mutex_get.c` |100.00% (70/70) |100.00% (26/26) |100.00% (5/5) +|`common/src/tx_mutex_priority_change.c` |99.35% (152/153) |94.92% (112/118) |100.00% (102/102) +|`common/src/tx_mutex_put.c` |98.35% (119/121) |88.33% (53/60) |90.91% (10/11) +|`common/src/tx_thread_initialize.c` |100.00% (17/17) |100.00% (6/6) |100.00% (1/1) +|`common/src/tx_thread_priority_change.c` |100.00% (68/68) |84.38% (27/32) |100.00% (18/18) +|`common_smp/src/tx_mutex_cleanup.c` |100.00% (47/47) |95.83% (23/24) |100.00% (4/4) +|`common_smp/src/tx_mutex_create.c` |100.00% (20/20) |100.00% (4/4) |100.00% (1/1) +|`common_smp/src/tx_mutex_get.c` |100.00% (70/70) |100.00% (26/26) |100.00% (5/5) +|`common_smp/src/tx_mutex_priority_change.c` |98.91% (182/184) |95.51% (149/156) |100.00% (102/102) +|`common_smp/src/tx_mutex_put.c` |98.35% (119/121) |88.33% (53/60) |90.91% (10/11) +|`common_smp/src/tx_thread_initialize.c` |100.00% (18/18) |100.00% (12/12) |100.00% (1/1) +|`common_smp/src/tx_thread_priority_change.c` |100.00% (121/121) |93.42% (71/76) |100.00% (19/19) +|=== + +[cols="1,2,1,1",options="header"] +|=== +|Kernel |Function |Lines |Branches +|`common` |`_tx_mutex_inherit_priority_update` |100.00% (12/12) |83.33% (5/6) +|`common` |`_tx_mutex_priority_chain` |100.00% (91/91) |95.35% (82/86) +|`common_smp` |`_tx_mutex_inherit_priority_update` |100.00% (12/12) |83.33% (5/6) +|`common_smp` |`_tx_mutex_priority_chain` |100.00% (91/91) |95.35% (82/86) +|=== + +The path solver and mutex updater execute their restoration paths, rather than +merely compiling them. Remaining uncovered branches include defensive invalid/null +wait-context checks and the null-owner guard, unreachable MISRA status retry arms, +and some existing scheduler/threshold paths. The legacy non-inheriting release +fallback in put is not reached after its earlier empty-list return. No overall +repository coverage floor or supported-host CI coverage percentage is claimed. +Per-configuration gcov output, compressed JSON, uncovered source line/branch lists, +and the union calculation are in `/tmp/threadx-808/coverage-final`. +The existing thread-priority-change regression passes in both adapted kernels +with chaining enabled and disabled (`priority-api-results.json`). + +[source,sh] +---- +# Actual collection command shape, one file in each configuration: +/opt/homebrew/bin/gcov-12 -b -c -f -j \ + /tmp/threadx-808/host-smp/threadx_smp/CMakeFiles/threadx_smp.dir/tmp/threadx-808/host-tree/common_smp/src/tx_mutex_priority_change.c.gcno +# The collection script repeats this for all 14 edited production C files. +python3 /tmp/threadx-808/collect-coverage.py +---- + +== Execution cost and limits + +K counts mutex edges beyond the first owner, so at most N=K+1 distinct owners +are visited. Path collection/membership checks cost O(N*N); the two edge passes +cost at most 2N relaxations. Let M and W be total owned mutexes and waiters +across visited owners. Seeding and cache publication cost O(M + N*W). +At most N actual scheduler priority changes occur per owner update. A transfer +updates two roots (its recipient is no longer waiting). Existing put prioritization +and scheduler work are additional; K does not bound their work or dynamic list sizes. +There are no allocations, recursive calls, global graph scans, or new retry loops. + +Graph processing holds interrupts/SMP protection and preemption disable for that +whole cost, including priority publication. Therefore K alone is not a constant +interrupt-latency bound: applications must bound mutex/waiter populations as well. +SMP incurs the corresponding shared protection hold time and existing scheduler +rebalance/IPI costs. No cycle-count or interrupt-latency measurement on hardware +was performed. + +Scratch arrays use `(K+1)*(sizeof(TX_THREAD *) + 2*sizeof(UINT))`, plus fixed locals +and callee frames. GCC `-O2 -fstack-usage` measured the solver's own frame at K=2 +as 128 bytes on RV64 GCC 13.2 and 112 bytes on the adapted SMP GCC 12 build +(144/128 bytes respectively with MISRA). These are function frames, not whole-call +stack bounds. The only new persistent storage is one optional function pointer +per kernel, 4 or 8 bytes according to the port. No public control-block layout changes. + +Undefined chaining adds no graph arrays, discovery, or edge relaxation. Get keeps +its prior single-level work. Cleanup and waiter-priority changes add the necessary +direct restoration/update scan; put reuses its owned-cache calculation. Those scans +now hold protection to avoid inconsistent cache snapshots. Transfer also records +valid remaining contributions in the recipient's inherited cache. + +A cycle fully inside the window loses obsolete external boosts. A cycle crossing +the configured boundary can feed a stale outside effective priority back into the +window; no claim of full restoration is made for that case. Likewise, owners beyond +the bound are not updated by an event. Applications needing full restoration must +configure K to cover their possible affected chains and cycles. Removing a donation +does not resolve a deadlock or make cycle members runnable. + +== PR readiness and remaining work + +The feature-plus-fix code/test patch applies cleanly to the locally available +`upstream/dev` snapshot (`a5b4739d`) in a temporary directory. Its RV64 build +and all 12 mutex tests pass there as well (`/tmp/threadx-808/dev-verify.log`). +The working branch, HEAD, and remotes remain unchanged. The GitHub-side action is +to ensure PR #808 targets `dev` (retarget if needed) and run the normal supported +Linux single-core/SMP CI; +no remote PR state was queried or changed. + +Remaining verification limits are the adapted host environment, its recorded +baseline/transient failures, untested physical SMP timing and custom port extension +hooks, and cycles/chains outside the configured propagation window. NI/MISRA/ +preemption-threshold combinations compile together; runtime tests cover the listed +individual combinations, not every possible macro combination. + +== Suggested reviewer response + +The issue was reproducible: with #807 alone, aborting H restored L1 to 20 but left +L2 at 5. The repair uses one inheritance updater for get, wait exit, configured +priority changes, and release/transfer. It recomputes a bounded owner path from +base priorities and valid outside donations before publishing priorities through +the existing single-core/SMP scheduler routines. Internal cached priorities are +excluded as seeds, which also removes self-sustaining boosts in contained cycles. +K continues to count edges beyond the first owner; cycles crossing that bound are +explicitly documented as a limitation. Regression tests cover timeout, abort, +termination, remaining donations, ownership transfer, priority changes, scheduling, +and a remotely executing SMP owner. The transfer tests also exposed and fixed the +SMP priority-change fast-path condition. Supported RV64 QEMU passes 99 tests; +adapted single-core/SMP mutex and configuration results, measured production +coverage, and all environment limitations are recorded above. Please retarget to +dev and run the supported Linux CI before merging. + +== Suggested commit + +[source,text] +---- +fix(mutex): restore inherited priorities through bounded mutex chains + +Waiter withdrawal restored only the first owner, leaving obsolete priorities +in downstream owners and mutually reinforcing cycle caches. + +Recompute the bounded owner path from user priorities and valid donations, +share the updater across mutex events, and retain native scheduler handling. +Correct the SMP user-priority fast path when inheritance becomes effective. + +Add wait-exit, chain restoration, transfer, bound, cycle, and remote-owner +regressions. Validate with RV64 QEMU, adapted host simulators, strict builds, +and measured production coverage; document host and cycle-bound limitations. + +Assisted-by: Codex (GPT-6) +---- diff --git a/test/smp/cmake/regression/CMakeLists.txt b/test/smp/cmake/regression/CMakeLists.txt index 93db84c88..05756fff3 100644 --- a/test/smp/cmake/regression/CMakeLists.txt +++ b/test/smp/cmake/regression/CMakeLists.txt @@ -50,6 +50,7 @@ set(regression_test_cases ${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_inherit_wait_exit_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 8f7771e09..35970fdb3 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -10,8 +10,6 @@ /***************************************************************************/ /* This is the test control routine of the ThreadX kernel. All tests are dispatched from this routine. */ -// Portions of this file were generated with AI assistance. - #define TX_THREAD_SMP_SOURCE_CODE #include "tx_api.h" @@ -197,6 +195,7 @@ 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_inherit_wait_exit_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 *); @@ -345,6 +344,7 @@ TEST_ENTRY test_control_tests[] = threadx_mutex_basic_application_define, threadx_mutex_inherit_chain_application_define, + threadx_mutex_inherit_wait_exit_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 index 24922dda4..eb314d538 100644 --- a/test/smp/regression/threadx_mutex_inherit_chain_test.c +++ b/test/smp/regression/threadx_mutex_inherit_chain_test.c @@ -32,11 +32,22 @@ static TX_THREAD owner[OWNERS]; static TX_MUTEX mutex[OWNERS]; static CHAR *thread_stack[OWNERS + 1]; static volatile UINT owner_spin; +static UINT owner_count = OWNERS; +static ULONG waiter_wait = TX_WAIT_FOREVER; +static volatile UINT waiter_status; +static TX_THREAD extra_waiter; +static TX_MUTEX extra_mutex; +static CHAR *extra_stack; +static UINT take_extra; +static UINT release_first; +static volatile UINT medium_ran; + /* Define thread prototypes. */ static void control_thread_entry(ULONG thread_input); +static void medium_entry(ULONG input); /* Prototype for test control return. */ @@ -73,6 +84,9 @@ UINT i; pointer = pointer + TEST_STACK_SIZE_PRINTF; } + extra_stack = pointer; + status += tx_mutex_create(&extra_mutex, "extra", TX_INHERIT); + for (i = 0; i < OWNERS; i++) { status += tx_mutex_create(&mutex[i], "chain mutex", TX_INHERIT); @@ -93,7 +107,7 @@ UINT i; static void waiter_entry(ULONG thread_input) { - tx_mutex_get(&mutex[0], TX_WAIT_FOREVER); + waiter_status = tx_mutex_get(&mutex[0], waiter_wait); tx_thread_suspend(tx_thread_identify()); } @@ -104,11 +118,20 @@ static void chain_owner_entry(ULONG thread_input) { tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); - if (thread_input + 1 < OWNERS) + if ((thread_input == 0) && take_extra) + { + tx_mutex_get(&extra_mutex, TX_WAIT_FOREVER); + } + if (thread_input + 1 < owner_count) { tx_mutex_get(&mutex[thread_input + 1], TX_WAIT_FOREVER); } tx_thread_suspend(tx_thread_identify()); + if ((thread_input == 0) && release_first) + { + tx_mutex_put(&mutex[0]); + } + tx_thread_suspend(tx_thread_identify()); } @@ -162,6 +185,7 @@ static void start_thread(TX_THREAD *thread_ptr, VOID (*entry)(ULONG), ULONG i static void start_waiter(void) { + waiter_status = 0xFFFF; start_thread(&waiter_thread, waiter_entry, 0, OWNERS, 5); WAIT_UNTIL(waiter_thread.tx_thread_state == TX_MUTEX_SUSP) } @@ -186,7 +210,7 @@ UINT i; /* 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. */ + owner i only if the chain reaches i mutex edges past owner 0. */ static UINT inherits(UINT i) { @@ -224,6 +248,12 @@ UINT i; check(owner[i].tx_thread_priority == (inherits(i) ? 5 : 20 - i), error + 1 + i); } + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 5); + for (i = 0; i < OWNERS; i++) + { + /* Beyond the bound neither the boost nor its withdrawal is applied. */ + check(owner[i].tx_thread_priority == 20 - i, error + 6 + i); + } stop_threads(OWNERS); } @@ -235,17 +265,57 @@ static void chain_to_ready_owner(UINT error) { owner_spin = 1; - start_thread(&owner[1], spinning_owner_entry, 1, 1, 20); + start_thread(&owner[1], spinning_owner_entry, 1, 1, 30); +#if defined(TX_THREAD_SMP_MAX_CORES) && TX_THREAD_SMP_MAX_CORES > 1 + /* Exercise inheritance and restoration of an owner executing on another core. */ + check(tx_thread_smp_core_exclude(&owner[1], 1) == TX_SUCCESS, error + 14); +#endif WAIT_UNTIL(mutex[1].tx_mutex_owner == &owner[1]) - start_thread(&owner[0], chain_owner_entry, 0, 0, 15); + start_thread(&owner[0], chain_owner_entry, 0, 0, 20); 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); + check(owner[1].tx_thread_priority == 20, 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); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 20), error + 3); + + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 4); + printf("\nAfter H abort: L1=%u L2=%u (expected 20,20)\n", + owner[0].tx_thread_priority, owner[1].tx_thread_priority); + check(owner[0].tx_thread_priority == 20, error + 5); + check(owner[1].tx_thread_priority == 20, error + 6); + + /* Crossing an inherited priority from a stronger user priority must + also restore a ready (and, on SMP, remotely executing) owner. */ + { + UINT old; + check(tx_thread_priority_change(&owner[1], 15, &old) == TX_SUCCESS, error + 15); + check(owner[1].tx_thread_priority == 15, error + 16); + check(tx_thread_priority_change(&owner[1], 30, &old) == TX_SUCCESS, error + 17); + check(owner[1].tx_thread_priority == 20, error + 18); + } + /* A runnable thread at 10 must now outrank the ready owner at 20. */ + { + UINT old; + medium_ran = 0; + check(tx_thread_create(&extra_waiter, "medium", medium_entry, 0, + extra_stack, TEST_STACK_SIZE_PRINTF, 10, 10, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, error + 10); + check(tx_thread_priority_change(&control_thread, 12, &old) == TX_SUCCESS, error + 11); +#ifdef TX_THREAD_SMP_MAX_CORES + WAIT_UNTIL(medium_ran) +#endif + check(medium_ran == 1, error + 12); + check(tx_thread_priority_change(&control_thread, 1, &old) == TX_SUCCESS, error + 13); + tx_thread_terminate(&extra_waiter); + tx_thread_delete(&extra_waiter); + } + + /* Removing L1's wait must also remove its remaining contribution. */ + check(tx_thread_wait_abort(&owner[0]) == TX_SUCCESS, error + 7); + check(owner[1].tx_thread_priority == 30, error + 8); + check(mutex[1].tx_mutex_suspended_count == 0, error + 9); owner_spin = 0; WAIT_UNTIL(owner[1].tx_thread_state == TX_SUSPENDED) stop_threads(2); @@ -270,9 +340,210 @@ static void chain_with_cycle(UINT error) check(owner[0].tx_thread_priority == 5, error); check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 15), error + 1); +#if defined(TX_MUTEX_INHERIT_CHAIN_MAX) && TX_MUTEX_INHERIT_CHAIN_MAX >= 1 + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 2); + check(owner[0].tx_thread_priority == 15, error + 3); + check(owner[1].tx_thread_priority == 15, error + 4); + check(owner[0].tx_thread_state == TX_MUTEX_SUSP, error + 5); + check(owner[1].tx_thread_state == TX_MUTEX_SUSP, error + 6); + check(mutex[0].tx_mutex_suspended_count == 1, error + 7); + check(mutex[1].tx_mutex_suspended_count == 1, error + 8); + check(owner[0].tx_thread_suspended_next == &owner[0], error + 13); + check(owner[1].tx_thread_suspended_next == &owner[1], error + 14); + /* A base-priority decrease inside the cycle must discard its own old boost. */ + { + UINT old; + check(tx_thread_priority_change(&owner[0], 25, &old) == TX_SUCCESS, error + 9); + check(old == 15, error + 10); + check(owner[0].tx_thread_priority == 20, error + 11); + check(owner[1].tx_thread_priority == 20, error + 12); + } +#endif + stop_threads(2); +} + + + +static void medium_entry(ULONG input) +{ + medium_ran = 1; + tx_thread_suspend(tx_thread_identify()); +} + +/* A second waiter can depend on mutex A or on another mutex owned by L1. */ +static void extra_waiter_entry(ULONG input) +{ + tx_mutex_get(input ? &extra_mutex : &mutex[0], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + +static void start_owners(UINT count) +{ +UINT i; + + owner_count = count; + for (i = count; i > 0; i--) + { + start_thread(&owner[i - 1], chain_owner_entry, i - 1, i - 1, 20 + 3 * (i - 1)); + WAIT_UNTIL(owner[i - 1].tx_thread_state == ((i == count) ? TX_SUSPENDED : TX_MUTEX_SUSP)) + check(mutex[i - 1].tx_mutex_owner == &owner[i - 1], 100); + check(owner[i - 1].tx_thread_state == ((i == count) ? TX_SUSPENDED : TX_MUTEX_SUSP), 101); + } +} + +/* T1-T4: timeout, abort and termination, with two and three owners. */ +static void restore_chain(UINT count, UINT how, UINT error) +{ +UINT i; + + start_owners(count); + waiter_wait = (how == 0) ? 50 : TX_WAIT_FOREVER; + start_waiter(); + check(waiter_thread.tx_thread_state == TX_MUTEX_SUSP, error); + for (i = 0; i < count; i++) + { + check(owner[i].tx_thread_priority == (inherits(i) ? 5 : 20), error + 1 + i); + } + if (how == 1) + { + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 5); + } + else if (how == 2) + { + check(tx_thread_terminate(&waiter_thread) == TX_SUCCESS, error + 6); + check(waiter_thread.tx_thread_state == TX_TERMINATED, error + 7); + } + if (how != 2) + { + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == ((how == 0) ? TX_NOT_AVAILABLE : TX_WAIT_ABORTED), error + 8); + } + check(mutex[0].tx_mutex_suspended_count == 0, error + 9); + check(mutex[0].tx_mutex_suspension_list == TX_NULL, error + 10); + check(mutex[1].tx_mutex_suspension_list == &owner[0], error + 14); + check(owner[0].tx_thread_suspended_next == &owner[0], error + 15); + check(owner[0].tx_thread_suspended_previous == &owner[0], error + 16); + check(owner[0].tx_thread_owned_mutex_count == 1, error + 17); + for (i = 0; i < count; i++) + { + check(owner[i].tx_thread_priority == 20, error + 11 + i); + } + stop_threads(count); + owner_count = OWNERS; + waiter_wait = TX_WAIT_FOREVER; +} + +/* T5-T7: retain other waiters, including another owned mutex, and handle + both directions of API priority change while the dependency is active. */ +static void remaining_contributions(UINT another_mutex, UINT error) +{ +UINT old; + + take_extra = another_mutex; + start_owners(2); + check(tx_thread_create(&extra_waiter, "extra waiter", extra_waiter_entry, another_mutex, + extra_stack, TEST_STACK_SIZE_PRINTF, 10, 10, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, error); + WAIT_UNTIL(extra_waiter.tx_thread_state == TX_MUTEX_SUSP) + check(extra_waiter.tx_thread_state == TX_MUTEX_SUSP, error + 1); + start_waiter(); + check(owner[0].tx_thread_priority == 5, error + 2); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 20), error + 3); + check(tx_thread_priority_change(&waiter_thread, 12, &old) == TX_SUCCESS, error + 4); + check(old == 5, error + 5); + check(owner[0].tx_thread_priority == 10, error + 6); + check(owner[1].tx_thread_priority == (inherits(1) ? 10 : 20), error + 7); + check(tx_thread_priority_change(&waiter_thread, 4, &old) == TX_SUCCESS, error + 8); + check(old == 12, error + 9); + check(owner[0].tx_thread_priority == 4, error + 10); + check(owner[1].tx_thread_priority == (inherits(1) ? 4 : 20), error + 11); + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 12); + check(owner[0].tx_thread_priority == 10, error + 13); + check(owner[1].tx_thread_priority == (inherits(1) ? 10 : 20), error + 14); + check(tx_thread_wait_abort(&extra_waiter) == TX_SUCCESS, error + 15); + check(owner[0].tx_thread_priority == 20, error + 16); + check(owner[1].tx_thread_priority == 20, error + 17); + tx_thread_terminate(&extra_waiter); + tx_thread_delete(&extra_waiter); + stop_threads(2); + take_extra = 0; + owner_count = OWNERS; +} + +/* T8: release A after removing L1's wait on B. H becomes A's owner; + both the former owner and B's owner must retain only valid donations. */ +static void ownership_transfer(UINT error) +{ + start_owners(2); + start_waiter(); + check(tx_thread_create(&extra_waiter, "transfer waiter", extra_waiter_entry, 0, + extra_stack, TEST_STACK_SIZE_PRINTF, 10, 10, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, error + 7); + start_thread(&owner[3], extra_waiter_entry, 0, 3, 15); + WAIT_UNTIL(mutex[0].tx_mutex_suspended_count == 3) + check(mutex[0].tx_mutex_suspended_count == 3, error + 8); + release_first = 1; + check(tx_thread_wait_abort(&owner[0]) == TX_SUCCESS, error); + check(owner[1].tx_thread_priority == 23, error + 1); + WAIT_UNTIL(owner[0].tx_thread_state == TX_SUSPENDED) + check(owner[0].tx_thread_state == TX_SUSPENDED, error + 2); + check(tx_thread_resume(&owner[0]) == TX_SUCCESS, error + 3); + WAIT_UNTIL(mutex[0].tx_mutex_owner == &waiter_thread) + check(mutex[0].tx_mutex_owner == &waiter_thread, error + 4); + check(owner[0].tx_thread_priority == 20, error + 5); + check(owner[0].tx_thread_inherit_priority == TX_MAX_PRIORITIES, error + 6); + /* The transferred mutex must contribute its remaining waiters to H's + inheritance, including when H's own priority subsequently drops. */ + { + UINT old; + check(waiter_thread.tx_thread_inherit_priority == 10, error + 9); + check(tx_thread_priority_change(&waiter_thread, 18, &old) == TX_SUCCESS, error + 10); + check(waiter_thread.tx_thread_priority == 10, error + 11); + check(tx_thread_terminate(&extra_waiter) == TX_SUCCESS, error + 12); + check(waiter_thread.tx_thread_priority == 15, error + 13); + check(tx_thread_terminate(&owner[3]) == TX_SUCCESS, error + 14); + check(waiter_thread.tx_thread_priority == 18, error + 15); + } + tx_thread_delete(&extra_waiter); + tx_thread_delete(&owner[3]); + stop_threads(2); + release_first = 0; + owner_count = OWNERS; +} + +/* Kernel deletion releases an owner's mutex while that owner still waits on B. */ +static void delete_while_waiting(UINT error) +{ + start_owners(2); + start_waiter(); + check(tx_mutex_delete(&mutex[0]) == TX_SUCCESS, error); + check(owner[0].tx_thread_state == TX_MUTEX_SUSP, error + 1); + check(owner[0].tx_thread_priority == 20, error + 2); + check(owner[1].tx_thread_priority == 20, error + 3); + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == TX_DELETED, error + 4); + check(mutex[1].tx_mutex_suspended_count == 1, error + 5); + check(mutex[1].tx_mutex_suspension_list == &owner[0], error + 6); stop_threads(2); + check(tx_mutex_create(&mutex[0], "inherit", TX_INHERIT) == TX_SUCCESS, error + 7); + owner_count = OWNERS; } +/* A non-inheriting edge ends the path and preserves its FIFO list. */ +static void non_inheriting_edge(UINT error) +{ + check(tx_mutex_delete(&mutex[1]) == TX_SUCCESS, error); + check(tx_mutex_create(&mutex[1], "no inherit", TX_NO_INHERIT) == TX_SUCCESS, error + 1); + start_owners(2); + start_waiter(); + check(owner[0].tx_thread_priority == 5, error + 2); + check(owner[1].tx_thread_priority == 23, error + 3); + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 4); + check(owner[0].tx_thread_priority == 20, error + 5); + check(owner[1].tx_thread_priority == 23, error + 6); + stop_threads(2); + check(tx_mutex_delete(&mutex[1]) == TX_SUCCESS, error + 7); + check(tx_mutex_create(&mutex[1], "inherit", TX_INHERIT) == TX_SUCCESS, error + 8); + owner_count = OWNERS; +} static void control_thread_entry(ULONG thread_input) { @@ -280,9 +551,22 @@ 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); + chain_of_suspended_owners(10); + chain_with_cycle(50); + restore_chain(2, 0, 110); + restore_chain(2, 1, 130); + restore_chain(2, 2, 150); +#if defined(TX_MUTEX_INHERIT_CHAIN_MAX) && TX_MUTEX_INHERIT_CHAIN_MAX >= 2 + restore_chain(3, 0, 170); + restore_chain(3, 1, 190); + restore_chain(3, 2, 210); +#endif + remaining_contributions(0, 230); + remaining_contributions(1, 250); + ownership_transfer(270); + non_inheriting_edge(310); + delete_while_waiting(330); /* Successful test. */ printf("SUCCESS!\n"); diff --git a/test/smp/regression/threadx_mutex_inherit_wait_exit_test.c b/test/smp/regression/threadx_mutex_inherit_wait_exit_test.c new file mode 100644 index 000000000..d0d7c26e8 --- /dev/null +++ b/test/smp/regression/threadx_mutex_inherit_wait_exit_test.c @@ -0,0 +1,232 @@ +/*************************************************************************** + * 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 a priority-inheritance mutex owner gives up an inherited + priority when the thread it inherited it from stops waiting without obtaining the + mutex: through a wait timeout, tx_thread_wait_abort or tx_thread_terminate. The + owner must drop to the highest priority still waiting on any mutex it owns, or to + its own priority if none is. */ + +#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 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; +static TX_MUTEX mutex_1; +static ULONG waiter_wait_option; +static volatile UINT waiter_status; + + +/* 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_wait_exit_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 <= WAITERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + status += tx_mutex_create(&mutex_1, "mutex 1", TX_INHERIT); + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Wait Exit Test................................ ERROR #1\n"); + test_control_return(1); + } +} + + +/* The owner takes both mutexes and parks. */ + +static void owner_thread_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); + tx_thread_suspend(&owner_thread); +} + + +/* A waiter blocks on mutex 0, or on mutex 1 if its input is 1. */ + +static void waiter_entry(ULONG thread_input) +{ + + waiter_status = tx_mutex_get((thread_input == 0) ? &mutex_0 : &mutex_1, waiter_wait_option); + 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); +} + + +static void start_owner(void) +{ + + start_thread(&owner_thread, owner_thread_entry, 0, WAITERS, 15); + WAIT_UNTIL((mutex_1.tx_mutex_owner == &owner_thread) && (owner_thread.tx_thread_state == TX_SUSPENDED)) +} + + +/* The owner at 15 holds mutex 0, and waiter 0 at 5 blocks on it and then leaves + without it. how: 0 is a wait timeout, 1 is tx_thread_wait_abort, 2 is + tx_thread_terminate. */ + +static void single_waiter_leaves(UINT how, UINT error) +{ + + start_owner(); + waiter_wait_option = (how == 0) ? 50 : TX_WAIT_FOREVER; + waiter_status = 0xFFFF; + start_thread(&waiter[0], waiter_entry, 0, 0, 5); + WAIT_UNTIL(waiter[0].tx_thread_state == TX_MUTEX_SUSP) + check(owner_thread.tx_thread_priority == 5, error); + + if (how == 1) + { + tx_thread_wait_abort(&waiter[0]); + } + else if (how == 2) + { + tx_thread_terminate(&waiter[0]); + } + + if (how != 2) + { + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == ((how == 0) ? TX_NOT_AVAILABLE : TX_WAIT_ABORTED), error + 1); + } + + /* Nothing waits on the mutex any more, so the owner is back at its own priority. */ + check(mutex_0.tx_mutex_highest_priority_waiting == TX_MAX_PRIORITIES, error + 2); + check(owner_thread.tx_thread_priority == 15, error + 3); + + tx_thread_terminate(&waiter[0]); + tx_thread_delete(&waiter[0]); + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +/* The owner at 15 holds both mutexes. Waiters at 5, 8 and 20 block on mutex 0 and + one at 12 on mutex 1. As they are aborted in turn, the owner keeps the highest + priority still waiting on either mutex. */ + +static void waiters_leave_in_turn(UINT error) +{ + +static const UINT priority[WAITERS] = {5, 8, 20, 12}; +static const UINT abort_order[WAITERS] = {2, 0, 1, 3}; +static const UINT owner_priority[WAITERS] = {5, 8, 12, 15}; +UINT i; + + + start_owner(); + waiter_wait_option = TX_WAIT_FOREVER; + for (i = 0; i < WAITERS; i++) + { + start_thread(&waiter[i], waiter_entry, (i == 3) ? 1 : 0, i, priority[i]); + } + WAIT_UNTIL((mutex_0.tx_mutex_suspended_count == 3) && (mutex_1.tx_mutex_suspended_count == 1)) + check(owner_thread.tx_thread_priority == 5, error); + + for (i = 0; i < WAITERS; i++) + { + tx_thread_wait_abort(&waiter[abort_order[i]]); + check(owner_thread.tx_thread_priority == owner_priority[i], error + 1 + i); + } + + for (i = 0; i < WAITERS; i++) + { + tx_thread_terminate(&waiter[i]); + tx_thread_delete(&waiter[i]); + } + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + /* Inform user. */ + printf("Running Mutex Inherit Wait Exit Test................................ "); + + single_waiter_leaves(0, 10); + single_waiter_leaves(1, 20); + single_waiter_leaves(2, 30); + waiters_leave_in_turn(40); + + /* 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 04ae2403f..ecfda252c 100644 --- a/test/tx/cmake/regression/CMakeLists.txt +++ b/test/tx/cmake/regression/CMakeLists.txt @@ -47,6 +47,7 @@ set(regression_test_cases ${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_inherit_wait_exit_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/cmake/riscv/regression/CMakeLists.txt b/test/tx/cmake/riscv/regression/CMakeLists.txt index b15247ffb..cd495e23c 100644 --- a/test/tx/cmake/riscv/regression/CMakeLists.txt +++ b/test/tx/cmake/riscv/regression/CMakeLists.txt @@ -48,6 +48,8 @@ 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_inherit_wait_exit_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 bb2f7dea0..f1724411c 100644 --- a/test/tx/regression/testcontrol.c +++ b/test/tx/regression/testcontrol.c @@ -10,8 +10,6 @@ /***************************************************************************/ /* This is the test control routine of the ThreadX kernel. All tests are dispatched from this routine. */ -// Portions of this file were generated with AI assistance. - #include "tx_api.h" #include #include "tx_initialize.h" @@ -169,6 +167,7 @@ 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_inherit_wait_exit_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 *); @@ -289,6 +288,7 @@ TEST_ENTRY test_control_tests[] = threadx_mutex_basic_application_define, threadx_mutex_inherit_chain_application_define, + threadx_mutex_inherit_wait_exit_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 index 24922dda4..eb314d538 100644 --- a/test/tx/regression/threadx_mutex_inherit_chain_test.c +++ b/test/tx/regression/threadx_mutex_inherit_chain_test.c @@ -32,11 +32,22 @@ static TX_THREAD owner[OWNERS]; static TX_MUTEX mutex[OWNERS]; static CHAR *thread_stack[OWNERS + 1]; static volatile UINT owner_spin; +static UINT owner_count = OWNERS; +static ULONG waiter_wait = TX_WAIT_FOREVER; +static volatile UINT waiter_status; +static TX_THREAD extra_waiter; +static TX_MUTEX extra_mutex; +static CHAR *extra_stack; +static UINT take_extra; +static UINT release_first; +static volatile UINT medium_ran; + /* Define thread prototypes. */ static void control_thread_entry(ULONG thread_input); +static void medium_entry(ULONG input); /* Prototype for test control return. */ @@ -73,6 +84,9 @@ UINT i; pointer = pointer + TEST_STACK_SIZE_PRINTF; } + extra_stack = pointer; + status += tx_mutex_create(&extra_mutex, "extra", TX_INHERIT); + for (i = 0; i < OWNERS; i++) { status += tx_mutex_create(&mutex[i], "chain mutex", TX_INHERIT); @@ -93,7 +107,7 @@ UINT i; static void waiter_entry(ULONG thread_input) { - tx_mutex_get(&mutex[0], TX_WAIT_FOREVER); + waiter_status = tx_mutex_get(&mutex[0], waiter_wait); tx_thread_suspend(tx_thread_identify()); } @@ -104,11 +118,20 @@ static void chain_owner_entry(ULONG thread_input) { tx_mutex_get(&mutex[thread_input], TX_WAIT_FOREVER); - if (thread_input + 1 < OWNERS) + if ((thread_input == 0) && take_extra) + { + tx_mutex_get(&extra_mutex, TX_WAIT_FOREVER); + } + if (thread_input + 1 < owner_count) { tx_mutex_get(&mutex[thread_input + 1], TX_WAIT_FOREVER); } tx_thread_suspend(tx_thread_identify()); + if ((thread_input == 0) && release_first) + { + tx_mutex_put(&mutex[0]); + } + tx_thread_suspend(tx_thread_identify()); } @@ -162,6 +185,7 @@ static void start_thread(TX_THREAD *thread_ptr, VOID (*entry)(ULONG), ULONG i static void start_waiter(void) { + waiter_status = 0xFFFF; start_thread(&waiter_thread, waiter_entry, 0, OWNERS, 5); WAIT_UNTIL(waiter_thread.tx_thread_state == TX_MUTEX_SUSP) } @@ -186,7 +210,7 @@ UINT i; /* 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. */ + owner i only if the chain reaches i mutex edges past owner 0. */ static UINT inherits(UINT i) { @@ -224,6 +248,12 @@ UINT i; check(owner[i].tx_thread_priority == (inherits(i) ? 5 : 20 - i), error + 1 + i); } + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 5); + for (i = 0; i < OWNERS; i++) + { + /* Beyond the bound neither the boost nor its withdrawal is applied. */ + check(owner[i].tx_thread_priority == 20 - i, error + 6 + i); + } stop_threads(OWNERS); } @@ -235,17 +265,57 @@ static void chain_to_ready_owner(UINT error) { owner_spin = 1; - start_thread(&owner[1], spinning_owner_entry, 1, 1, 20); + start_thread(&owner[1], spinning_owner_entry, 1, 1, 30); +#if defined(TX_THREAD_SMP_MAX_CORES) && TX_THREAD_SMP_MAX_CORES > 1 + /* Exercise inheritance and restoration of an owner executing on another core. */ + check(tx_thread_smp_core_exclude(&owner[1], 1) == TX_SUCCESS, error + 14); +#endif WAIT_UNTIL(mutex[1].tx_mutex_owner == &owner[1]) - start_thread(&owner[0], chain_owner_entry, 0, 0, 15); + start_thread(&owner[0], chain_owner_entry, 0, 0, 20); 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); + check(owner[1].tx_thread_priority == 20, 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); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 20), error + 3); + + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 4); + printf("\nAfter H abort: L1=%u L2=%u (expected 20,20)\n", + owner[0].tx_thread_priority, owner[1].tx_thread_priority); + check(owner[0].tx_thread_priority == 20, error + 5); + check(owner[1].tx_thread_priority == 20, error + 6); + + /* Crossing an inherited priority from a stronger user priority must + also restore a ready (and, on SMP, remotely executing) owner. */ + { + UINT old; + check(tx_thread_priority_change(&owner[1], 15, &old) == TX_SUCCESS, error + 15); + check(owner[1].tx_thread_priority == 15, error + 16); + check(tx_thread_priority_change(&owner[1], 30, &old) == TX_SUCCESS, error + 17); + check(owner[1].tx_thread_priority == 20, error + 18); + } + /* A runnable thread at 10 must now outrank the ready owner at 20. */ + { + UINT old; + medium_ran = 0; + check(tx_thread_create(&extra_waiter, "medium", medium_entry, 0, + extra_stack, TEST_STACK_SIZE_PRINTF, 10, 10, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, error + 10); + check(tx_thread_priority_change(&control_thread, 12, &old) == TX_SUCCESS, error + 11); +#ifdef TX_THREAD_SMP_MAX_CORES + WAIT_UNTIL(medium_ran) +#endif + check(medium_ran == 1, error + 12); + check(tx_thread_priority_change(&control_thread, 1, &old) == TX_SUCCESS, error + 13); + tx_thread_terminate(&extra_waiter); + tx_thread_delete(&extra_waiter); + } + + /* Removing L1's wait must also remove its remaining contribution. */ + check(tx_thread_wait_abort(&owner[0]) == TX_SUCCESS, error + 7); + check(owner[1].tx_thread_priority == 30, error + 8); + check(mutex[1].tx_mutex_suspended_count == 0, error + 9); owner_spin = 0; WAIT_UNTIL(owner[1].tx_thread_state == TX_SUSPENDED) stop_threads(2); @@ -270,9 +340,210 @@ static void chain_with_cycle(UINT error) check(owner[0].tx_thread_priority == 5, error); check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 15), error + 1); +#if defined(TX_MUTEX_INHERIT_CHAIN_MAX) && TX_MUTEX_INHERIT_CHAIN_MAX >= 1 + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 2); + check(owner[0].tx_thread_priority == 15, error + 3); + check(owner[1].tx_thread_priority == 15, error + 4); + check(owner[0].tx_thread_state == TX_MUTEX_SUSP, error + 5); + check(owner[1].tx_thread_state == TX_MUTEX_SUSP, error + 6); + check(mutex[0].tx_mutex_suspended_count == 1, error + 7); + check(mutex[1].tx_mutex_suspended_count == 1, error + 8); + check(owner[0].tx_thread_suspended_next == &owner[0], error + 13); + check(owner[1].tx_thread_suspended_next == &owner[1], error + 14); + /* A base-priority decrease inside the cycle must discard its own old boost. */ + { + UINT old; + check(tx_thread_priority_change(&owner[0], 25, &old) == TX_SUCCESS, error + 9); + check(old == 15, error + 10); + check(owner[0].tx_thread_priority == 20, error + 11); + check(owner[1].tx_thread_priority == 20, error + 12); + } +#endif + stop_threads(2); +} + + + +static void medium_entry(ULONG input) +{ + medium_ran = 1; + tx_thread_suspend(tx_thread_identify()); +} + +/* A second waiter can depend on mutex A or on another mutex owned by L1. */ +static void extra_waiter_entry(ULONG input) +{ + tx_mutex_get(input ? &extra_mutex : &mutex[0], TX_WAIT_FOREVER); + tx_thread_suspend(tx_thread_identify()); +} + +static void start_owners(UINT count) +{ +UINT i; + + owner_count = count; + for (i = count; i > 0; i--) + { + start_thread(&owner[i - 1], chain_owner_entry, i - 1, i - 1, 20 + 3 * (i - 1)); + WAIT_UNTIL(owner[i - 1].tx_thread_state == ((i == count) ? TX_SUSPENDED : TX_MUTEX_SUSP)) + check(mutex[i - 1].tx_mutex_owner == &owner[i - 1], 100); + check(owner[i - 1].tx_thread_state == ((i == count) ? TX_SUSPENDED : TX_MUTEX_SUSP), 101); + } +} + +/* T1-T4: timeout, abort and termination, with two and three owners. */ +static void restore_chain(UINT count, UINT how, UINT error) +{ +UINT i; + + start_owners(count); + waiter_wait = (how == 0) ? 50 : TX_WAIT_FOREVER; + start_waiter(); + check(waiter_thread.tx_thread_state == TX_MUTEX_SUSP, error); + for (i = 0; i < count; i++) + { + check(owner[i].tx_thread_priority == (inherits(i) ? 5 : 20), error + 1 + i); + } + if (how == 1) + { + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 5); + } + else if (how == 2) + { + check(tx_thread_terminate(&waiter_thread) == TX_SUCCESS, error + 6); + check(waiter_thread.tx_thread_state == TX_TERMINATED, error + 7); + } + if (how != 2) + { + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == ((how == 0) ? TX_NOT_AVAILABLE : TX_WAIT_ABORTED), error + 8); + } + check(mutex[0].tx_mutex_suspended_count == 0, error + 9); + check(mutex[0].tx_mutex_suspension_list == TX_NULL, error + 10); + check(mutex[1].tx_mutex_suspension_list == &owner[0], error + 14); + check(owner[0].tx_thread_suspended_next == &owner[0], error + 15); + check(owner[0].tx_thread_suspended_previous == &owner[0], error + 16); + check(owner[0].tx_thread_owned_mutex_count == 1, error + 17); + for (i = 0; i < count; i++) + { + check(owner[i].tx_thread_priority == 20, error + 11 + i); + } + stop_threads(count); + owner_count = OWNERS; + waiter_wait = TX_WAIT_FOREVER; +} + +/* T5-T7: retain other waiters, including another owned mutex, and handle + both directions of API priority change while the dependency is active. */ +static void remaining_contributions(UINT another_mutex, UINT error) +{ +UINT old; + + take_extra = another_mutex; + start_owners(2); + check(tx_thread_create(&extra_waiter, "extra waiter", extra_waiter_entry, another_mutex, + extra_stack, TEST_STACK_SIZE_PRINTF, 10, 10, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, error); + WAIT_UNTIL(extra_waiter.tx_thread_state == TX_MUTEX_SUSP) + check(extra_waiter.tx_thread_state == TX_MUTEX_SUSP, error + 1); + start_waiter(); + check(owner[0].tx_thread_priority == 5, error + 2); + check(owner[1].tx_thread_priority == (inherits(1) ? 5 : 20), error + 3); + check(tx_thread_priority_change(&waiter_thread, 12, &old) == TX_SUCCESS, error + 4); + check(old == 5, error + 5); + check(owner[0].tx_thread_priority == 10, error + 6); + check(owner[1].tx_thread_priority == (inherits(1) ? 10 : 20), error + 7); + check(tx_thread_priority_change(&waiter_thread, 4, &old) == TX_SUCCESS, error + 8); + check(old == 12, error + 9); + check(owner[0].tx_thread_priority == 4, error + 10); + check(owner[1].tx_thread_priority == (inherits(1) ? 4 : 20), error + 11); + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 12); + check(owner[0].tx_thread_priority == 10, error + 13); + check(owner[1].tx_thread_priority == (inherits(1) ? 10 : 20), error + 14); + check(tx_thread_wait_abort(&extra_waiter) == TX_SUCCESS, error + 15); + check(owner[0].tx_thread_priority == 20, error + 16); + check(owner[1].tx_thread_priority == 20, error + 17); + tx_thread_terminate(&extra_waiter); + tx_thread_delete(&extra_waiter); + stop_threads(2); + take_extra = 0; + owner_count = OWNERS; +} + +/* T8: release A after removing L1's wait on B. H becomes A's owner; + both the former owner and B's owner must retain only valid donations. */ +static void ownership_transfer(UINT error) +{ + start_owners(2); + start_waiter(); + check(tx_thread_create(&extra_waiter, "transfer waiter", extra_waiter_entry, 0, + extra_stack, TEST_STACK_SIZE_PRINTF, 10, 10, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, error + 7); + start_thread(&owner[3], extra_waiter_entry, 0, 3, 15); + WAIT_UNTIL(mutex[0].tx_mutex_suspended_count == 3) + check(mutex[0].tx_mutex_suspended_count == 3, error + 8); + release_first = 1; + check(tx_thread_wait_abort(&owner[0]) == TX_SUCCESS, error); + check(owner[1].tx_thread_priority == 23, error + 1); + WAIT_UNTIL(owner[0].tx_thread_state == TX_SUSPENDED) + check(owner[0].tx_thread_state == TX_SUSPENDED, error + 2); + check(tx_thread_resume(&owner[0]) == TX_SUCCESS, error + 3); + WAIT_UNTIL(mutex[0].tx_mutex_owner == &waiter_thread) + check(mutex[0].tx_mutex_owner == &waiter_thread, error + 4); + check(owner[0].tx_thread_priority == 20, error + 5); + check(owner[0].tx_thread_inherit_priority == TX_MAX_PRIORITIES, error + 6); + /* The transferred mutex must contribute its remaining waiters to H's + inheritance, including when H's own priority subsequently drops. */ + { + UINT old; + check(waiter_thread.tx_thread_inherit_priority == 10, error + 9); + check(tx_thread_priority_change(&waiter_thread, 18, &old) == TX_SUCCESS, error + 10); + check(waiter_thread.tx_thread_priority == 10, error + 11); + check(tx_thread_terminate(&extra_waiter) == TX_SUCCESS, error + 12); + check(waiter_thread.tx_thread_priority == 15, error + 13); + check(tx_thread_terminate(&owner[3]) == TX_SUCCESS, error + 14); + check(waiter_thread.tx_thread_priority == 18, error + 15); + } + tx_thread_delete(&extra_waiter); + tx_thread_delete(&owner[3]); + stop_threads(2); + release_first = 0; + owner_count = OWNERS; +} + +/* Kernel deletion releases an owner's mutex while that owner still waits on B. */ +static void delete_while_waiting(UINT error) +{ + start_owners(2); + start_waiter(); + check(tx_mutex_delete(&mutex[0]) == TX_SUCCESS, error); + check(owner[0].tx_thread_state == TX_MUTEX_SUSP, error + 1); + check(owner[0].tx_thread_priority == 20, error + 2); + check(owner[1].tx_thread_priority == 20, error + 3); + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == TX_DELETED, error + 4); + check(mutex[1].tx_mutex_suspended_count == 1, error + 5); + check(mutex[1].tx_mutex_suspension_list == &owner[0], error + 6); stop_threads(2); + check(tx_mutex_create(&mutex[0], "inherit", TX_INHERIT) == TX_SUCCESS, error + 7); + owner_count = OWNERS; } +/* A non-inheriting edge ends the path and preserves its FIFO list. */ +static void non_inheriting_edge(UINT error) +{ + check(tx_mutex_delete(&mutex[1]) == TX_SUCCESS, error); + check(tx_mutex_create(&mutex[1], "no inherit", TX_NO_INHERIT) == TX_SUCCESS, error + 1); + start_owners(2); + start_waiter(); + check(owner[0].tx_thread_priority == 5, error + 2); + check(owner[1].tx_thread_priority == 23, error + 3); + check(tx_thread_wait_abort(&waiter_thread) == TX_SUCCESS, error + 4); + check(owner[0].tx_thread_priority == 20, error + 5); + check(owner[1].tx_thread_priority == 23, error + 6); + stop_threads(2); + check(tx_mutex_delete(&mutex[1]) == TX_SUCCESS, error + 7); + check(tx_mutex_create(&mutex[1], "inherit", TX_INHERIT) == TX_SUCCESS, error + 8); + owner_count = OWNERS; +} static void control_thread_entry(ULONG thread_input) { @@ -280,9 +551,22 @@ 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); + chain_of_suspended_owners(10); + chain_with_cycle(50); + restore_chain(2, 0, 110); + restore_chain(2, 1, 130); + restore_chain(2, 2, 150); +#if defined(TX_MUTEX_INHERIT_CHAIN_MAX) && TX_MUTEX_INHERIT_CHAIN_MAX >= 2 + restore_chain(3, 0, 170); + restore_chain(3, 1, 190); + restore_chain(3, 2, 210); +#endif + remaining_contributions(0, 230); + remaining_contributions(1, 250); + ownership_transfer(270); + non_inheriting_edge(310); + delete_while_waiting(330); /* Successful test. */ printf("SUCCESS!\n"); diff --git a/test/tx/regression/threadx_mutex_inherit_wait_exit_test.c b/test/tx/regression/threadx_mutex_inherit_wait_exit_test.c new file mode 100644 index 000000000..d0d7c26e8 --- /dev/null +++ b/test/tx/regression/threadx_mutex_inherit_wait_exit_test.c @@ -0,0 +1,232 @@ +/*************************************************************************** + * 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 a priority-inheritance mutex owner gives up an inherited + priority when the thread it inherited it from stops waiting without obtaining the + mutex: through a wait timeout, tx_thread_wait_abort or tx_thread_terminate. The + owner must drop to the highest priority still waiting on any mutex it owns, or to + its own priority if none is. */ + +#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 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; +static TX_MUTEX mutex_1; +static ULONG waiter_wait_option; +static volatile UINT waiter_status; + + +/* 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_wait_exit_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 <= WAITERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + status += tx_mutex_create(&mutex_1, "mutex 1", TX_INHERIT); + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Wait Exit Test................................ ERROR #1\n"); + test_control_return(1); + } +} + + +/* The owner takes both mutexes and parks. */ + +static void owner_thread_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); + tx_thread_suspend(&owner_thread); +} + + +/* A waiter blocks on mutex 0, or on mutex 1 if its input is 1. */ + +static void waiter_entry(ULONG thread_input) +{ + + waiter_status = tx_mutex_get((thread_input == 0) ? &mutex_0 : &mutex_1, waiter_wait_option); + 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); +} + + +static void start_owner(void) +{ + + start_thread(&owner_thread, owner_thread_entry, 0, WAITERS, 15); + WAIT_UNTIL((mutex_1.tx_mutex_owner == &owner_thread) && (owner_thread.tx_thread_state == TX_SUSPENDED)) +} + + +/* The owner at 15 holds mutex 0, and waiter 0 at 5 blocks on it and then leaves + without it. how: 0 is a wait timeout, 1 is tx_thread_wait_abort, 2 is + tx_thread_terminate. */ + +static void single_waiter_leaves(UINT how, UINT error) +{ + + start_owner(); + waiter_wait_option = (how == 0) ? 50 : TX_WAIT_FOREVER; + waiter_status = 0xFFFF; + start_thread(&waiter[0], waiter_entry, 0, 0, 5); + WAIT_UNTIL(waiter[0].tx_thread_state == TX_MUTEX_SUSP) + check(owner_thread.tx_thread_priority == 5, error); + + if (how == 1) + { + tx_thread_wait_abort(&waiter[0]); + } + else if (how == 2) + { + tx_thread_terminate(&waiter[0]); + } + + if (how != 2) + { + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == ((how == 0) ? TX_NOT_AVAILABLE : TX_WAIT_ABORTED), error + 1); + } + + /* Nothing waits on the mutex any more, so the owner is back at its own priority. */ + check(mutex_0.tx_mutex_highest_priority_waiting == TX_MAX_PRIORITIES, error + 2); + check(owner_thread.tx_thread_priority == 15, error + 3); + + tx_thread_terminate(&waiter[0]); + tx_thread_delete(&waiter[0]); + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +/* The owner at 15 holds both mutexes. Waiters at 5, 8 and 20 block on mutex 0 and + one at 12 on mutex 1. As they are aborted in turn, the owner keeps the highest + priority still waiting on either mutex. */ + +static void waiters_leave_in_turn(UINT error) +{ + +static const UINT priority[WAITERS] = {5, 8, 20, 12}; +static const UINT abort_order[WAITERS] = {2, 0, 1, 3}; +static const UINT owner_priority[WAITERS] = {5, 8, 12, 15}; +UINT i; + + + start_owner(); + waiter_wait_option = TX_WAIT_FOREVER; + for (i = 0; i < WAITERS; i++) + { + start_thread(&waiter[i], waiter_entry, (i == 3) ? 1 : 0, i, priority[i]); + } + WAIT_UNTIL((mutex_0.tx_mutex_suspended_count == 3) && (mutex_1.tx_mutex_suspended_count == 1)) + check(owner_thread.tx_thread_priority == 5, error); + + for (i = 0; i < WAITERS; i++) + { + tx_thread_wait_abort(&waiter[abort_order[i]]); + check(owner_thread.tx_thread_priority == owner_priority[i], error + 1 + i); + } + + for (i = 0; i < WAITERS; i++) + { + tx_thread_terminate(&waiter[i]); + tx_thread_delete(&waiter[i]); + } + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + /* Inform user. */ + printf("Running Mutex Inherit Wait Exit Test................................ "); + + single_waiter_leaves(0, 10); + single_waiter_leaves(1, 20); + single_waiter_leaves(2, 30); + waiters_leave_in_turn(40); + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +}