summaryrefslogtreecommitdiffstats
path: root/testsuites/validation/tc-score-tq-smp.c
diff options
context:
space:
mode:
Diffstat (limited to 'testsuites/validation/tc-score-tq-smp.c')
-rw-r--r--testsuites/validation/tc-score-tq-smp.c571
1 files changed, 571 insertions, 0 deletions
diff --git a/testsuites/validation/tc-score-tq-smp.c b/testsuites/validation/tc-score-tq-smp.c
new file mode 100644
index 0000000000..624ad0e7b6
--- /dev/null
+++ b/testsuites/validation/tc-score-tq-smp.c
@@ -0,0 +1,571 @@
+/* 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 );
+}
+
+/** @} */