/*************************************************************************** * 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. * * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). * The AI-generated portions may be considered public domain (CC0-1.0) * and not subject to the project's licence. The human contributor has * reviewed and verified that the code is correct. * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ /* Define the ThreadX SMP relinquish rebalance test. This test fills every core with threads of one priority that relinquish in a loop, and places two more threads of the same priority behind them in the ready list: one that no core may run, and one that any core may run. Walking the ready list, a relinquish reaches the unrunnable thread first and cannot schedule it, so the execute list has to be rebalanced for the runnable thread to reach a core. The runnable thread never running is the failure this test detects. */ #include #include "tx_api.h" /* Fill the four cores, so a thread behind these in the ready list only reaches a core through the execute list being rebalanced. */ #define SPIN_THREADS 4 /* The spinning threads keep their cores whatever else happens, so they carry the deadline. One second is three orders of magnitude more than the handful of relinquishes a healthy system needs, and the tick advances independently of which threads are scheduled. */ #define DEADLINE_TICKS 100 static TX_THREAD thread_control; static TX_THREAD thread_spin[SPIN_THREADS]; static TX_THREAD thread_blocked; static TX_THREAD thread_starved; static ULONG spin_counter[SPIN_THREADS]; static ULONG starved_counter; static ULONG blocked_counter; static ULONG reported; /* Define thread prototypes. */ static void thread_control_entry(ULONG thread_input); static void thread_spin_entry(ULONG thread_input); static void thread_blocked_entry(ULONG thread_input); static void thread_starved_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_smp_relinquish_rebalance_test(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; /* Create the control thread, which only resumes the others and then completes. */ status = tx_thread_create(&thread_control, "control thread", thread_control_entry, 0, pointer, TEST_STACK_SIZE_PRINTF, 0, 0, TX_NO_TIME_SLICE, TX_AUTO_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; status += tx_thread_smp_core_exclude(&thread_control, 0xE); /* Core 0 only! */ /* Check status. */ if (status != TX_SUCCESS) { printf("Running SMP Relinquish Rebalance Test............................... ERROR #1\n"); test_control_return(1); } /* Create one spinning thread per core. */ for (i = 0; i < SPIN_THREADS; i++) { status = tx_thread_create(&thread_spin[i], "spin thread", thread_spin_entry, i, pointer, TEST_STACK_SIZE_PRINTF, 20, 20, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check status. */ if (status != TX_SUCCESS) { printf("Running SMP Relinquish Rebalance Test............................... ERROR #2\n"); test_control_return(1); } } /* Create the thread no core may run. Excluding every core leaves it ready and permanently unmapped, which is what makes it a stable obstacle in the ready list rather than a thread that eventually gets a turn. */ status = tx_thread_create(&thread_blocked, "blocked thread", thread_blocked_entry, 0, pointer, TEST_STACK_SIZE_PRINTF, 20, 20, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; status += tx_thread_smp_core_exclude(&thread_blocked, 0xF); /* No core at all! */ /* Check status. */ if (status != TX_SUCCESS) { printf("Running SMP Relinquish Rebalance Test............................... ERROR #3\n"); test_control_return(1); } /* Create the thread that any core may run. It is resumed last so that it sits behind the blocked thread in the ready list at this priority. */ status = tx_thread_create(&thread_starved, "starved thread", thread_starved_entry, 0, pointer, TEST_STACK_SIZE_PRINTF, 20, 20, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check status. */ if (status != TX_SUCCESS) { printf("Running SMP Relinquish Rebalance Test............................... ERROR #4\n"); test_control_return(1); } } /* Resume the others in ready list order and complete, so core 0 is released. */ static void thread_control_entry(ULONG thread_input) { UINT status; UINT i; status = TX_SUCCESS; /* Fill the cores first. */ for (i = 0; i < SPIN_THREADS; i++) { status += tx_thread_resume(&thread_spin[i]); } /* Then the obstacle, then the thread behind it. */ status += tx_thread_resume(&thread_blocked); status += tx_thread_resume(&thread_starved); /* Check status. */ if (status != TX_SUCCESS) { printf("Running SMP Relinquish Rebalance Test............................... ERROR #5\n"); test_control_return(1); } } /* Hold a core and relinquish. These threads keep running whatever the scheduler does with the rest, so they are the ones that can enforce a deadline on the starved thread. */ static void thread_spin_entry(ULONG thread_input) { ULONG start; start = tx_time_get(); while (starved_counter == 0) { spin_counter[thread_input]++; /* The tick advances whether or not any thread is being scheduled, so this bounds the test even when nothing is. */ if ((tx_time_get() - start) > DEADLINE_TICKS) { if (reported == 0) { reported = 1; printf("Running SMP Relinquish Rebalance Test............................... ERROR #6\n"); test_control_return(1); } return; } tx_thread_relinquish(); } } /* Never runs: every core is excluded for this thread. */ static void thread_blocked_entry(ULONG thread_input) { blocked_counter++; } /* Reaching a core at all is the pass condition. */ static void thread_starved_entry(ULONG thread_input) { starved_counter++; if (reported == 0) { reported = 1; /* The blocked thread must not have run, or the obstacle was not an obstacle and the test proved nothing. */ if (blocked_counter != 0) { printf("Running SMP Relinquish Rebalance Test............................... ERROR #7\n"); test_control_return(1); } printf("Running SMP Relinquish Rebalance Test............................... SUCCESS!\n"); test_control_return(0); } }