/*************************************************************************** * Copyright (c) 2024 Microsoft Corporation * * 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 is designed to test the mutex suspension and timeout functionality. */ #include #include "tx_api.h" #include "tx_thread.h" #include "tx_mutex.h" //static unsigned long thread_0_counter = 0; static TX_THREAD thread_0; static TX_THREAD thread_1; static TX_THREAD thread_2; static TX_THREAD thread_3; static TX_THREAD thread_4; static TX_THREAD low_priority; static TX_MUTEX mutex_0; static TX_MUTEX mutex_1; extern UINT mutex_priority_change_extension_selection; /* Define thread prototypes. */ static void thread_0_entry(ULONG thread_input); static void thread_1_entry(ULONG thread_input); static void thread_2_entry(ULONG thread_input); static void thread_3_entry(ULONG thread_input); static void thread_4_entry(ULONG thread_input); static void low_priority_entry(ULONG thread_input); /* Prototype for test control return. */ extern void test_control_return(UINT status); #ifndef TX_MANUAL_TEST /* Define test flags for automated test. */ extern TEST_FLAG threadx_mutex_suspension_put_test; extern TEST_FLAG threadx_mutex_suspension_priority_test; #endif /* This test routine is used to get NULL suspension lists in various parts of tx_mutex_put. This is hooked up to IRQ 0 on this simulation and is entered manually at the correct time. */ void abort_all_threads_suspended_on_mutex(void) { TX_THREAD *thread_ptr; thread_ptr = _tx_thread_created_ptr; while (thread_ptr) { if (thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) tx_thread_wait_abort(thread_ptr); thread_ptr = thread_ptr -> tx_thread_created_next; if (thread_ptr == _tx_thread_created_ptr) break; } } /* This test routine is used to get a thread of a non ready state into _tx_mutex_change, called froim _tx_mutex_put. This is hooked up to IRQ 1 on this simulation and is entered manually at the correct time. */ void suspend_lowest_priority(void) { TX_INTERRUPT_SAVE_AREA TX_THREAD *thread_ptr; /* Determine which extension to perform... to get to the different error checks in _tx_mutex_priority_change. */ if (mutex_priority_change_extension_selection == 0) { /* Setup the thread pointer. */ thread_ptr = &thread_0; /* Disable interrupts. */ TX_DISABLE /* Set the state to suspended. */ thread_ptr -> tx_thread_state = TX_SUSPENDED; #ifdef TX_NOT_INTERRUPTABLE /* Call actual non-interruptable thread suspension routine. */ _tx_thread_system_ni_suspend(thread_ptr, ((ULONG) 0)); /* Restore interrupts. */ TX_RESTORE #else /* Set the suspending flag. */ thread_ptr -> tx_thread_suspending = TX_TRUE; /* Setup for no timeout period. */ thread_ptr -> tx_thread_timer.tx_timer_internal_remaining_ticks = ((ULONG) 0); /* Temporarily disable preemption. */ _tx_thread_preempt_disable++; /* Restore interrupts. */ TX_RESTORE /* Call actual thread suspension routine. */ _tx_thread_system_suspend(thread_ptr); #endif } else if (mutex_priority_change_extension_selection == 1) { /* Make the mapped core field to be invalid to exercise the error checking branch for core_index. */ thread_0.tx_thread_smp_core_mapped = TX_THREAD_SMP_MAX_CORES; } } /* Define what the initial system looks like. */ #ifdef CTEST void test_application_define(void *first_unused_memory) #else void threadx_mutex_suspension_timeout_application_define(void *first_unused_memory) #endif { UINT status; CHAR *pointer; /* Put first available memory address into a character pointer. */ pointer = (CHAR *) first_unused_memory; /* Put system definition stuff in here, e.g. thread creates and other assorted create information. */ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, pointer, TEST_STACK_SIZE_PRINTF, 16, 16, 100, TX_AUTO_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; status += tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1, pointer, TEST_STACK_SIZE_PRINTF, 1, 1, 100, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; status += tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2, pointer, TEST_STACK_SIZE_PRINTF, 2, 2, 100, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; status += tx_thread_create(&thread_3, "thread 3", thread_3_entry, 3, pointer, TEST_STACK_SIZE_PRINTF, 3, 3, 100, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; status += tx_thread_create(&thread_4, "thread 4", thread_4_entry, 4, pointer, TEST_STACK_SIZE_PRINTF, 4, 4, 100, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; status += tx_thread_create(&low_priority, "low priority", low_priority_entry, 30, pointer, TEST_STACK_SIZE_PRINTF, 30, 30, 100, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Mutex Suspension Timeout Test............................... ERROR #1\n"); test_control_return(1); } /* Create mutexes. */ status = tx_mutex_create(&mutex_0, "mutex 0", TX_NO_INHERIT); status += tx_mutex_create(&mutex_1, "mutex 1", TX_INHERIT); /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Mutex Suspension Timeout Test............................... ERROR #2\n"); test_control_return(1); } /* Get the mutex to prevent thread from getting it. */ tx_mutex_get(&mutex_0, TX_NO_WAIT); } /* Define the test threads. */ static void thread_0_entry(ULONG thread_input) { TX_INTERRUPT_SAVE_AREA TX_THREAD *temp_thread; UINT status; ULONG expiry_time; /* Inform user. */ printf("Running Mutex Suspension Timeout Test............................... "); /* Sleep for 2 ticks for fresh timer. */ tx_thread_sleep(2); /* Set clock to 0. */ tx_time_set(0); /* Suspend on the mutex. */ status = tx_mutex_get(&mutex_0, 33); /* Did we get the right status at the right time? The suspension must not end before its 33 tick timeout, and that lower bound is the kernel property this checks. The upper bound is not: the clock is read after this thread has been given a core again, and on this port the tick comes from a host timer thread, so the read can land a tick or two beyond the expiry without anything having gone wrong. An exact equality here fails on that latency alone. */ expiry_time = tx_time_get(); if ((status != TX_NOT_AVAILABLE) || (expiry_time < 33) || (expiry_time > 35)) { /* Mutex error. */ printf("ERROR #3\n"); test_control_return(1); } /* Get the mutex. */ status = tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); /* Make sure the three higher priority threads suspend on the mutex. */ tx_thread_resume(&thread_4); tx_thread_resume(&thread_3); tx_thread_resume(&thread_2); tx_thread_resume(&thread_1); #ifdef TX_MANUAL_TEST /* Set BP hear and step into code and generate appropriate interrupt IRQ 0 by hand right after the prioritize function with interrupts enabled. This is to achieve the empty list branch coverage after the prioritize function. */ tx_mutex_put(&mutex_1); #else /* Set the flag such that generate the condition wehre the empty list condition is satisfied in the branch coverage. */ threadx_mutex_suspension_put_test = 1; tx_mutex_put(&mutex_1); #endif /* Now some hand testing for tx_mutex_priority_change. */ /* Resume the low priority thread. */ tx_thread_resume(&low_priority); /* Disable interrupts. */ TX_DISABLE /* Simulate a call from inside of mutex put, but doing it here makes life easier. */ _tx_thread_preempt_disable++; /* Move the current thread to a lower priority. */ _tx_mutex_priority_change(&thread_0, 15); _tx_mutex_priority_change(&thread_0, 16); /* Move the lower priority thread to a higher priority. */ _tx_mutex_priority_change(&low_priority, 28); _tx_mutex_priority_change(&low_priority, 29); _tx_mutex_priority_change(&low_priority, 30); /* Change the priority of the current thread, such that we should have reverse the preemption-threshold issue. */ thread_0.tx_thread_user_priority = 30; thread_0.tx_thread_user_preempt_threshold = 30; _tx_mutex_priority_change(&thread_0, 30); /* Change to an even lower priority. */ thread_0.tx_thread_user_priority = 31; thread_0.tx_thread_user_preempt_threshold = 31; _tx_mutex_priority_change(&thread_0, 31); /* Move back to a higher-priority. */ thread_0.tx_thread_user_priority = 30; thread_0.tx_thread_user_preempt_threshold = 30; _tx_mutex_priority_change(&thread_0, 16); #ifdef TX_MANUAL_TEST /* Set BP here and step into code and step through the code until the internal thread resume function returns, then issue an IRQ 1 to cause an ISR to suspend the thread and test the first condition. */ _tx_mutex_priority_change(&thread_0, 30); #else /* Set the flag to suspend the thread and test the first condition after internal resume is called. */ mutex_priority_change_extension_selection = 0; threadx_mutex_suspension_priority_test = 1; _tx_mutex_priority_change(&thread_0, 30); #endif /* Resume this thread. */ tx_thread_resume(&thread_0); _tx_mutex_priority_change(&thread_0, 16); /* Change to an even lower priority. */ thread_0.tx_thread_user_priority = 27; thread_0.tx_thread_user_preempt_threshold = 27; _tx_mutex_priority_change(&thread_0, 28); /* Setup the low priority thread infromation. */ low_priority.tx_thread_user_priority = 30; low_priority.tx_thread_user_preempt_threshold = 29; _tx_mutex_priority_change(&low_priority, 31); /* Change to an even lower priority. */ thread_0.tx_thread_user_priority = 30; thread_0.tx_thread_user_preempt_threshold = 29; _tx_mutex_priority_change(&thread_0, 26); #ifdef TX_MANUAL_TEST /* Set BP here and step into code and step through the code until the internal thread resume function returns, and then issue the IRQ that modifies the core mapped. */ mutex_priority_change_extension_selection = 1; _tx_mutex_priority_change(&thread_0, 30); thread_0.tx_thread_smp_core_mapped = 0; #else /* Set the flag to suspend the thread and test the first condition after internal resume is called. */ mutex_priority_change_extension_selection = 1; threadx_mutex_suspension_priority_test = 1; _tx_mutex_priority_change(&thread_0, 30); thread_0.tx_thread_smp_core_mapped = 0; #endif /* Move the thread back to priority 28. */ _tx_mutex_priority_change(&thread_0, 28); #ifdef TX_MANUAL_TEST /* Set BP here and step into code and step through the code until the internal thread resume function returns, and then issue the IRQ that modifies the original priority. */ mutex_priority_change_extension_selection = 2; _tx_mutex_priority_change(&thread_0, 29); #else /* Set the flag to suspend the thread and test the first condition after internal resume is called. */ mutex_priority_change_extension_selection = 2; threadx_mutex_suspension_priority_test = 1; _tx_mutex_priority_change(&thread_0, 29); _tx_thread_execute_ptr[0] = &thread_0; #endif #ifdef TX_MANUAL_TEST /* Set BP here and step into code and step through the code until the internal thread resume function returns, and then issue the IRQ that sets the original_pt_thread to NULL. */ mutex_priority_change_extension_selection = 3; _tx_mutex_priority_change(&thread_0, 30); #else /* Set the flag to suspend the thread and test the first condition after internal resume is called. */ mutex_priority_change_extension_selection = 3; threadx_mutex_suspension_priority_test = 1; _tx_mutex_priority_change(&thread_0, 30); #endif #ifdef TX_MANUAL_TEST /* Set BP here and step into code and step through the code until the internal thread resume function returns, and then issue the IRQ that sets the _tx_thread_preemption__threshold_scheduled to NULL. */ temp_thread = _tx_thread_preemption__threshold_scheduled; mutex_priority_change_extension_selection = 4; _tx_mutex_priority_change(&thread_0, 31); _tx_thread_preemption__threshold_scheduled = temp_thread; #else /* Set the flag to suspend the thread and test the first condition after internal resume is called. */ temp_thread = _tx_thread_preemption__threshold_scheduled; mutex_priority_change_extension_selection = 4; threadx_mutex_suspension_priority_test = 1; _tx_mutex_priority_change(&thread_0, 31); _tx_thread_preemption__threshold_scheduled = temp_thread; #endif /* Just change to same priority to ensure that path is executed. */ mutex_priority_change_extension_selection = 0; suspend_lowest_priority(); thread_0.tx_thread_user_priority = 31; thread_0.tx_thread_user_preempt_threshold = 30; thread_0.tx_thread_priority = 31; thread_0.tx_thread_preempt_threshold = 30; _tx_mutex_priority_change(&thread_0, 31); thread_0.tx_thread_user_priority = 31; thread_0.tx_thread_user_preempt_threshold = 31; thread_0.tx_thread_priority = 31; thread_0.tx_thread_preempt_threshold = 31; _tx_mutex_priority_change(&thread_0, 31); tx_thread_resume(&thread_0); /* Restore the preempt disable flag. */ _tx_thread_preempt_disable--; /* Restore interrupts. */ TX_RESTORE /* Successful test. */ printf("SUCCESS!\n"); test_control_return(0); } static void thread_1_entry(ULONG thread_input) { UINT status; /* Loop to get the mutex. */ while(1) { /* Get and release the mutex. */ status = tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); if (status == TX_SUCCESS) tx_mutex_put(&mutex_1); tx_thread_suspend(&thread_1); } } static void thread_2_entry(ULONG thread_input) { UINT status; /* Loop to get the mutex. */ while(1) { /* Get and release the mutex. */ status = tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); if (status == TX_SUCCESS) tx_mutex_put(&mutex_1); tx_thread_suspend(&thread_2); } } static void thread_3_entry(ULONG thread_input) { UINT status; /* Loop to get the mutex. */ while(1) { /* Get and release the mutex. */ status = tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); if (status == TX_SUCCESS) tx_mutex_put(&mutex_1); tx_thread_suspend(&thread_3); } } static void thread_4_entry(ULONG thread_input) { UINT status; /* Loop to get the mutex. */ while(1) { /* Get and release the mutex. */ status = tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); if (status == TX_SUCCESS) tx_mutex_put(&mutex_1); tx_thread_suspend(&thread_4); } } static void low_priority_entry(ULONG thread_input) { }