/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup ScoreTqValSmp
*/
/*
* Copyright (C) 2021 embedded brains GmbH & Co. KG
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This file is part of the RTEMS quality process and was automatically
* generated. If you find something that needs to be fixed or
* worded better please post a report or patch to an RTEMS mailing list
* or raise a bug report:
*
* https://www.rtems.org/bugs.html
*
* For information on updating and regenerating please refer to the How-To
* section in the Software Requirements Engineering chapter of the
* RTEMS Software Engineering manual. The manual is provided as a part of
* a release. For development sources please refer to the online
* documentation at:
*
* https://docs.rtems.org
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <rtems/score/smpbarrier.h>
#include <rtems/score/threadimpl.h>
#include <rtems/score/threadqimpl.h>
#include "tx-support.h"
#include <rtems/test.h>
/**
* @defgroup ScoreTqValSmp spec:/score/tq/val/smp
*
* @ingroup TestsuitesValidationSmpOnly0
*
* @brief Tests SMP-specific thread queue behaviour.
*
* This test case performs the following actions:
*
* - Create two or three worker threads and a mutex. Use the mutex and the
* worker to do a thread priority change in parallel with a thread queue
* extraction.
*
* - Create a mutex and let the runner obtain it.
*
* - Create and start worker A on a second processor. mutex. Let it wait on
* the barrier.
*
* - If there are more than two processors, then create and start also worker
* C. Let it wait on the barrier.
*
* - Create and start worker B. Let it try to obtain the mutex which is
* owned by the runner. Delete worker B to extract it from the thread
* queue. Wrap the thread queue extract operation to do a parallel thread
* priority change carried out by worker A (and maybe C).
*
* - Clean up all used resources.
*
* - Build a cyclic dependency graph using several worker threads and mutexes.
* Use the mutexes and the worker to construct a thread queue deadlock which
* is detected on one processor while it uses thread queue links inserted by
* another processor. The runner thread controls the test scenario via the
* two thread queue locks. This is an important test scenario which shows
* why the thread queue implementation is a bit more complicated in SMP
* configurations.
*
* - Let worker D wait for mutex A. Let worker C wait for mutex D. Let
* worker B wait for mutex C.
*
* - Let worker A attempt to obtain mutex B. Let worker A wait on the lock
* of mutex C. Worker A will insert two thread queue links.
*
* - Let worker E try to obtain mutex D. Worker E will add a thread queue
* link which is later used by worker A to detect the deadlock.
*
* - Let worker A continue the obtain sequence. It will detect a deadlock.
*
* - Clean up all used resources.
*
* @{
*/
/**
* @brief Test context for spec:/score/tq/val/smp test case.
*/
typedef struct {
/**
* @brief This member contains the runner identifier.
*/
rtems_id runner_id;
/**
* @brief This member contains the worker A identifier.
*/
rtems_id worker_a_id;
/**
* @brief This member contains the worker B identifier.
*/
rtems_id worker_b_id;
/**
* @brief This member contains the worker C identifier.
*/
rtems_id worker_c_id;
/**
* @brief This member contains the worker D identifier.
*/
rtems_id worker_d_id;
/**
* @brief This member contains the worker E identifier.
*/
rtems_id worker_e_id;
/**
* @brief This member contains the mutex A identifier.
*/
rtems_id mutex_a_id;
/**
* @brief This member contains the mutex B identifier.
*/
rtems_id mutex_b_id;
/**
* @brief This member contains the mutex C identifier.
*/
rtems_id mutex_c_id;
/**
* @brief This member contains the mutex D identifier.
*/
rtems_id mutex_d_id;
/**
* @brief This member contains the count of processors used by the test.
*/
uint32_t used_cpus;
/**
* @brief This member contains the thread queue of the mutex.
*/
Thread_queue_Queue *thread_queue;
/**
* @brief This member contains the context to wrap the thread queue extract.
*/
WrapThreadQueueContext wrap;
/**
* @brief This member contains the barrier to synchronize the runner and the
* workers.
*/
SMP_barrier_Control barrier;
/**
* @brief This member contains the barrier state for the runner processor.
*/
SMP_barrier_State barrier_state;
} ScoreTqValSmp_Context;
static ScoreTqValSmp_Context
ScoreTqValSmp_Instance;
typedef ScoreTqValSmp_Context Context;
static void Extract( void *arg )
{
Context *ctx;
ctx = arg;
/* PC0 */
_SMP_barrier_Wait( &ctx->barrier, &ctx->barrier_state, ctx->used_cpus );
/*
* Ensure that worker A (and maybe C) acquired the thread wait lock of
* worker B.
*/
TicketLockWaitForOthers( &ctx->thread_queue->Lock, ctx->used_cpus - 1 );
/*
* Continue with the thread queue extraction. The thread wait lock of
* worker B will be changed back to the default thread wait lock. This
* will cause worker A (and maybe C) to release the thread queue lock and
* acquire the default thread wait lock of worker B instead to carry out
* the priority change.
*
* See also _Thread_Wait_acquire_critical().
*/
}
static void PriorityChangeWorker( rtems_task_argument arg )
{
Context *ctx;
SMP_barrier_State state;
ctx = (Context *) arg;
_SMP_barrier_State_initialize( &state );
/* PC0 */
_SMP_barrier_Wait( &ctx->barrier, &state, ctx->used_cpus );
SetPriority( ctx->worker_b_id, PRIO_VERY_HIGH );
/* PC1 */
_SMP_barrier_Wait( &ctx->barrier, &state, ctx->used_cpus );
(void) ReceiveAnyEvents();
}
static void MutexObtainWorker( rtems_task_argument arg )
{
Context *ctx;
ctx = (Context *) arg;
ObtainMutex( ctx->mutex_a_id );
}
static void DeadlockWorkerA( rtems_task_argument arg )
{
Context *ctx;
SMP_barrier_State state;
ctx = (Context *) arg;
_SMP_barrier_State_initialize( &state );
ObtainMutex( ctx->mutex_a_id );
/* D0 */
_SMP_barrier_Wait( &ctx->barrier, &state, 2 );
/* D1 */
_SMP_barrier_Wait( &ctx->barrier, &state, 2 );
ObtainMutexDeadlock( ctx->mutex_b_id );
ReleaseMutex( ctx->mutex_a_id );
SendEvents( ctx->runner_id, RTEMS_EVENT_0 );
(void) ReceiveAnyEvents();
}
static void DeadlockWorkerB( rtems_task_argument arg )
{
Context *ctx;
ctx = (Context *) arg;
ObtainMutex( ctx->mutex_b_id );
SendEvents( ctx->runner_id, RTEMS_EVENT_5 );
ObtainMutex( ctx->mutex_c_id );
ReleaseMutex( ctx->mutex_c_id );
ReleaseMutex( ctx->mutex_b_id );
SendEvents( ctx->runner_id, RTEMS_EVENT_1 );
(void) ReceiveAnyEvents();
}
static void DeadlockWorkerC( rtems_task_argument arg )
{
Context *ctx;
ctx = (Context *) arg;
ObtainMutex( ctx->mutex_c_id );
ObtainMutex( ctx->mutex_d_id );
ReleaseMutex( ctx->mutex_d_id );
ReleaseMutex( ctx->mutex_c_id );
SendEvents( ctx->runner_id, RTEMS_EVENT_2 );
(void) ReceiveAnyEvents();
}
static void DeadlockWorkerD( rtems_task_argument arg )
{
Context *ctx;
ctx = (Context *) arg;
ObtainMutex( ctx->mutex_d_id );
ObtainMutex( ctx->mutex_a_id );
ReleaseMutex( ctx->mutex_a_id );
ReleaseMutex( ctx->mutex_d_id );
SendEvents( ctx->runner_id, RTEMS_EVENT_3 );
(void) ReceiveAnyEvents();
}
static void DeadlockWorkerE( rtems_task_argument arg )
{
Context *ctx;
ctx = (Context *) arg;
ObtainMutex( ctx->mutex_d_id );
ReleaseMutex( ctx->mutex_d_id );
SendEvents( ctx->runner_id, RTEMS_EVENT_4 );
(void) ReceiveAnyEvents();
}
static void ScoreTqValSmp_Setup( ScoreTqValSmp_Context *ctx )
{
SetSelfPriority( PRIO_NORMAL );
}
static void ScoreTqValSmp_Setup_Wrap( void *arg )
{
ScoreTqValSmp_Context *ctx;
ctx = arg;
ScoreTqValSmp_Setup( ctx );
}
static void ScoreTqValSmp_Teardown( ScoreTqValSmp_Context *ctx )
{
RestoreRunnerPriority();
}
static void ScoreTqValSmp_Teardown_Wrap( void *arg )
{
ScoreTqValSmp_Context *ctx;
ctx = arg;
ScoreTqValSmp_Teardown( ctx );
}
static T_fixture ScoreTqValSmp_Fixture = {
.setup = ScoreTqValSmp_Setup_Wrap,
.stop = NULL,
.teardown = ScoreTqValSmp_Teardown_Wrap,
.scope = NULL,
.initial_context = &ScoreTqValSmp_Instance
};
/**
* @brief Create two or three worker threads and a mutex. Use the mutex and
* the worker to do a thread priority change in parallel with a thread queue
* extraction.
*/
static void ScoreTqValSmp_Action_0( ScoreTqValSmp_Context *ctx )
{
_SMP_barrier_Control_initialize( &ctx->barrier );
_SMP_barrier_State_initialize( &ctx->barrier_state );
WrapThreadQueueInitialize( &ctx->wrap, Extract, ctx );
if ( rtems_scheduler_get_processor_maximum() > 2 ) {
ctx->used_cpus = 3;
} else {
ctx->used_cpus = 2;
}
/*
* Create a mutex and let the runner obtain it.
*/
ctx->mutex_a_id = CreateMutexNoProtocol();
ctx->thread_queue = GetMutexThreadQueue( ctx->mutex_a_id );
ObtainMutex( ctx->mutex_a_id );
/*
* Create and start worker A on a second processor. mutex. Let it wait on
* the barrier.
*/
ctx->worker_a_id = CreateTask( "WRKA", PRIO_NORMAL );
SetScheduler( ctx->worker_a_id, SCHEDULER_B_ID, PRIO_NORMAL );
StartTask( ctx->worker_a_id, PriorityChangeWorker, ctx );
/*
* If there are more than two processors, then create and start also worker
* C. Let it wait on the barrier.
*/
if ( ctx->used_cpus > 2 ) {
ctx->worker_c_id = CreateTask( "WRKC", PRIO_NORMAL );
SetScheduler( ctx->worker_c_id, SCHEDULER_C_ID, PRIO_NORMAL );
StartTask( ctx->worker_c_id, PriorityChangeWorker, ctx );
}
/*
* Create and start worker B. Let it try to obtain the mutex which is owned
* by the runner. Delete worker B to extract it from the thread queue. Wrap
* the thread queue extract operation to do a parallel thread priority change
* carried out by worker A (and maybe C).
*/
ctx->worker_b_id = CreateTask( "WRKB", PRIO_HIGH );
StartTask( ctx->worker_b_id, MutexObtainWorker, ctx );
WrapThreadQueueExtractDirect( &ctx->wrap, GetThread( ctx->worker_b_id ) );
DeleteTask( ctx->worker_b_id );
/*
* Clean up all used resources.
*/
/* PC1 */
_SMP_barrier_Wait( &ctx->barrier, &ctx->barrier_state, ctx->used_cpus );
WaitForExecutionStop( ctx->worker_a_id );
DeleteTask( ctx->worker_a_id );
if ( ctx->used_cpus > 2 ) {
WaitForExecutionStop( ctx->worker_c_id );
DeleteTask( ctx->worker_c_id );
}
ReleaseMutex( ctx->mutex_a_id );
DeleteMutex( ctx->mutex_a_id );
WrapThreadQueueDestroy( &ctx->wrap );
}
/**
* @brief Build a cyclic dependency graph using several worker threads and
* mutexes. Use the mutexes and the worker to construct a thread queue
* deadlock which is detected on one processor while it uses thread queue
* links inserted by another processor. The runner thread controls the test
* scenario via the two thread queue locks. This is an important test
* scenario which shows why the thread queue implementation is a bit more
* complicated in SMP configurations.
*/
static void ScoreTqValSmp_Action_1( ScoreTqValSmp_Context *ctx )
{
Thread_queue_Queue *queue_b;
Thread_queue_Queue *queue_c;
ISR_lock_Context lock_context;
SMP_barrier_State state;
if ( rtems_scheduler_get_processor_maximum() <= 2 ) {
/*
* We can only run this validation test on systems with three or more
* processors. The sequence under test can happen on systems with only two
* processors, however, we need a third processor to control the other two
* processors via ISR locks to get a deterministic test scenario.
*/
return;
}
ctx->runner_id = rtems_task_self();
_SMP_barrier_Control_initialize( &ctx->barrier );
_SMP_barrier_State_initialize( &state );
ctx->mutex_a_id = CreateMutexNoProtocol();
ctx->mutex_b_id = CreateMutexNoProtocol();
ctx->mutex_c_id = CreateMutexNoProtocol();
ctx->mutex_d_id = CreateMutexNoProtocol();
queue_b = GetMutexThreadQueue( ctx->mutex_b_id );
queue_c = GetMutexThreadQueue( ctx->mutex_c_id );
ctx->worker_a_id = CreateTask( "WRKA", PRIO_NORMAL );
ctx->worker_b_id = CreateTask( "WRKB", PRIO_NORMAL );
ctx->worker_c_id = CreateTask( "WRKC", PRIO_NORMAL );
ctx->worker_d_id = CreateTask( "WRKD", PRIO_NORMAL );
ctx->worker_e_id = CreateTask( "WRKE", PRIO_NORMAL );
SetScheduler( ctx->worker_a_id, SCHEDULER_B_ID, PRIO_NORMAL );
SetScheduler( ctx->worker_b_id, SCHEDULER_B_ID, PRIO_HIGH );
SetScheduler( ctx->worker_c_id, SCHEDULER_B_ID, PRIO_HIGH );
SetScheduler( ctx->worker_d_id, SCHEDULER_B_ID, PRIO_HIGH );
SetScheduler( ctx->worker_e_id, SCHEDULER_C_ID, PRIO_NORMAL );
/*
* Let worker D wait for mutex A. Let worker C wait for mutex D. Let worker
* B wait for mutex C.
*/
StartTask( ctx->worker_a_id, DeadlockWorkerA, ctx );
/* D0 */
_SMP_barrier_Wait( &ctx->barrier, &state, 2 );
StartTask( ctx->worker_d_id, DeadlockWorkerD, ctx );
StartTask( ctx->worker_c_id, DeadlockWorkerC, ctx );
StartTask( ctx->worker_b_id, DeadlockWorkerB, ctx );
ReceiveAllEvents( RTEMS_EVENT_5 );
WaitForExecutionStop( ctx->worker_b_id );
/*
* Let worker A attempt to obtain mutex B. Let worker A wait on the lock of
* mutex C. Worker A will insert two thread queue links.
*/
_ISR_lock_ISR_disable( &lock_context );
_Thread_queue_Queue_acquire_critical(
queue_c,
&_Thread_Executing->Potpourri_stats,
&lock_context
);
_ISR_lock_ISR_enable( &lock_context );
/* D1 */
_SMP_barrier_Wait( &ctx->barrier, &state, 2 );
TicketLockWaitForOthers( &queue_c->Lock, 1 );
/*
* Let worker E try to obtain mutex D. Worker E will add a thread queue link
* which is later used by worker A to detect the deadlock.
*/
StartTask( ctx->worker_e_id, DeadlockWorkerE, ctx );
TicketLockWaitForOthers( &queue_b->Lock, 1 );
/*
* Let worker A continue the obtain sequence. It will detect a deadlock.
*/
_ISR_lock_ISR_disable( &lock_context );
_Thread_queue_Queue_release( queue_c, &lock_context );
/*
* Clean up all used resources.
*/
ReceiveAllEvents(
RTEMS_EVENT_0 | RTEMS_EVENT_1 | RTEMS_EVENT_2 | RTEMS_EVENT_3 |
RTEMS_EVENT_4
);
WaitForExecutionStop( ctx->worker_a_id );
WaitForExecutionStop( ctx->worker_b_id );
WaitForExecutionStop( ctx->worker_c_id );
WaitForExecutionStop( ctx->worker_d_id );
WaitForExecutionStop( ctx->worker_e_id );
DeleteTask( ctx->worker_a_id );
DeleteTask( ctx->worker_b_id );
DeleteTask( ctx->worker_c_id );
DeleteTask( ctx->worker_d_id );
DeleteTask( ctx->worker_e_id );
DeleteMutex( ctx->mutex_a_id );
DeleteMutex( ctx->mutex_b_id );
DeleteMutex( ctx->mutex_c_id );
DeleteMutex( ctx->mutex_d_id );
}
/**
* @fn void T_case_body_ScoreTqValSmp( void )
*/
T_TEST_CASE_FIXTURE( ScoreTqValSmp, &ScoreTqValSmp_Fixture )
{
ScoreTqValSmp_Context *ctx;
ctx = T_fixture_context();
ScoreTqValSmp_Action_0( ctx );
ScoreTqValSmp_Action_1( ctx );
}
/** @} */