summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cpukit/score/include/rtems/score/thread.h7
-rw-r--r--cpukit/score/include/rtems/score/userextimpl.h108
-rw-r--r--cpukit/score/src/threadrestart.c2
-rw-r--r--cpukit/score/src/userextaddset.c14
-rw-r--r--cpukit/score/src/userextiterate.c84
-rw-r--r--cpukit/score/src/userextremoveset.c9
-rw-r--r--testsuites/sptests/Makefile.am1
-rw-r--r--testsuites/sptests/configure.ac1
-rw-r--r--testsuites/sptests/spextensions01/Makefile.am19
-rw-r--r--testsuites/sptests/spextensions01/init.c447
-rw-r--r--testsuites/sptests/spextensions01/spextensions01.doc12
-rw-r--r--testsuites/sptests/spextensions01/spextensions01.scn2
12 files changed, 666 insertions, 40 deletions
diff --git a/cpukit/score/include/rtems/score/thread.h b/cpukit/score/include/rtems/score/thread.h
index d9f1eb2d6d..4da4a341d6 100644
--- a/cpukit/score/include/rtems/score/thread.h
+++ b/cpukit/score/include/rtems/score/thread.h
@@ -48,6 +48,8 @@ struct Scheduler_Control;
struct Scheduler_Node;
+struct User_extensions_Iterator;
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -899,6 +901,11 @@ struct _Thread_Control {
struct _pthread_cleanup_context *last_cleanup_context;
/**
+ * @brief LIFO list of user extensions iterators.
+ */
+ struct User_extensions_Iterator *last_user_extensions_iterator;
+
+ /**
* @brief Variable length array of user extension pointers.
*
* The length is defined by the application via <rtems/confdefs.h>.
diff --git a/cpukit/score/include/rtems/score/userextimpl.h b/cpukit/score/include/rtems/score/userextimpl.h
index 8c2a1fac4d..e175c9f0a5 100644
--- a/cpukit/score/include/rtems/score/userextimpl.h
+++ b/cpukit/score/include/rtems/score/userextimpl.h
@@ -19,6 +19,7 @@
#define _RTEMS_SCORE_USEREXTIMPL_H
#include <rtems/score/userext.h>
+#include <rtems/score/isrlock.h>
#include <rtems/score/chainimpl.h>
#include <rtems/score/percpu.h>
@@ -36,9 +37,41 @@ extern "C" {
/**@{**/
/**
+ * @brief Chain iterator for dynamic user extensions.
+ *
+ * Since user extensions may delete or restart the executing thread, we must
+ * clean up registered iterators.
+ *
+ * @see _User_extensions_Iterate(), _User_extensions_Destroy_iterators() and
+ * Thread_Control::last_user_extensions_iterator.
+ */
+typedef struct User_extensions_Iterator {
+ Chain_Iterator Iterator;
+ struct User_extensions_Iterator *previous;
+} User_extensions_Iterator;
+
+typedef struct {
+ /**
+ * @brief Active dynamically added user extensions.
+ */
+ Chain_Control Active;
+
+ /**
+ * @brief Chain iterator registration.
+ */
+ Chain_Iterator_registry Iterators;
+
+ /**
+ * @brief Lock to protect User_extensions_List::Active and
+ * User_extensions_List::Iterators.
+ */
+ ISR_LOCK_MEMBER( Lock )
+} User_extensions_List;
+
+/**
* @brief List of active extensions.
*/
-extern Chain_Control _User_extensions_List;
+extern User_extensions_List _User_extensions_List;
/**
* @brief List of active task switch extensions.
@@ -153,11 +186,13 @@ void _User_extensions_Thread_terminate_visitor(
* @brief Iterates through all user extensions and calls the visitor for each.
*
* @param[in, out] arg The argument passed to the visitor.
- * @param[in] visitor is the visitor for each extension.
+ * @param[in] visitor The visitor for each extension.
+ * @param[in] direction The iteration direction for dynamic extensions.
*/
void _User_extensions_Iterate(
- void *arg,
- User_extensions_Visitor visitor
+ void *arg,
+ User_extensions_Visitor visitor,
+ Chain_Iterator_direction direction
);
/** @} */
@@ -171,7 +206,11 @@ static inline bool _User_extensions_Thread_create( Thread_Control *created )
{
User_extensions_Thread_create_context ctx = { created, true };
- _User_extensions_Iterate( &ctx, _User_extensions_Thread_create_visitor );
+ _User_extensions_Iterate(
+ &ctx,
+ _User_extensions_Thread_create_visitor,
+ CHAIN_ITERATOR_FORWARD
+ );
return ctx.ok;
}
@@ -180,7 +219,8 @@ static inline void _User_extensions_Thread_delete( Thread_Control *deleted )
{
_User_extensions_Iterate(
deleted,
- _User_extensions_Thread_delete_visitor
+ _User_extensions_Thread_delete_visitor,
+ CHAIN_ITERATOR_BACKWARD
);
}
@@ -188,7 +228,8 @@ static inline void _User_extensions_Thread_start( Thread_Control *started )
{
_User_extensions_Iterate(
started,
- _User_extensions_Thread_start_visitor
+ _User_extensions_Thread_start_visitor,
+ CHAIN_ITERATOR_FORWARD
);
}
@@ -196,7 +237,8 @@ static inline void _User_extensions_Thread_restart( Thread_Control *restarted )
{
_User_extensions_Iterate(
restarted,
- _User_extensions_Thread_restart_visitor
+ _User_extensions_Thread_restart_visitor,
+ CHAIN_ITERATOR_FORWARD
);
}
@@ -204,7 +246,8 @@ static inline void _User_extensions_Thread_begin( Thread_Control *executing )
{
_User_extensions_Iterate(
executing,
- _User_extensions_Thread_begin_visitor
+ _User_extensions_Thread_begin_visitor,
+ CHAIN_ITERATOR_FORWARD
);
}
@@ -239,7 +282,8 @@ static inline void _User_extensions_Thread_exitted( Thread_Control *executing )
{
_User_extensions_Iterate(
executing,
- _User_extensions_Thread_exitted_visitor
+ _User_extensions_Thread_exitted_visitor,
+ CHAIN_ITERATOR_FORWARD
);
}
@@ -251,7 +295,11 @@ static inline void _User_extensions_Fatal(
{
User_extensions_Fatal_context ctx = { source, is_internal, error };
- _User_extensions_Iterate( &ctx, _User_extensions_Fatal_visitor );
+ _User_extensions_Iterate(
+ &ctx,
+ _User_extensions_Fatal_visitor,
+ CHAIN_ITERATOR_BACKWARD
+ );
}
static inline void _User_extensions_Thread_terminate(
@@ -260,10 +308,46 @@ static inline void _User_extensions_Thread_terminate(
{
_User_extensions_Iterate(
executing,
- _User_extensions_Thread_terminate_visitor
+ _User_extensions_Thread_terminate_visitor,
+ CHAIN_ITERATOR_FORWARD
+ );
+}
+
+static inline void _User_extensions_Acquire( ISR_lock_Context *lock_context )
+{
+ _ISR_lock_ISR_disable_and_acquire(
+ &_User_extensions_List.Lock,
+ lock_context
);
}
+static inline void _User_extensions_Release( ISR_lock_Context *lock_context )
+{
+ _ISR_lock_Release_and_ISR_enable(
+ &_User_extensions_List.Lock,
+ lock_context
+ );
+}
+
+static inline void _User_extensions_Destroy_iterators(
+ Thread_Control *the_thread
+)
+{
+ ISR_lock_Context lock_context;
+ User_extensions_Iterator *iter;
+
+ _User_extensions_Acquire( &lock_context );
+
+ iter = the_thread->last_user_extensions_iterator;
+
+ while ( iter != NULL ) {
+ _Chain_Iterator_destroy( &iter->Iterator );
+ iter = iter->previous;
+ }
+
+ _User_extensions_Release( &lock_context );
+}
+
/** @} */
/** @} */
diff --git a/cpukit/score/src/threadrestart.c b/cpukit/score/src/threadrestart.c
index 59754a86fb..13b4365b35 100644
--- a/cpukit/score/src/threadrestart.c
+++ b/cpukit/score/src/threadrestart.c
@@ -97,6 +97,7 @@ static void _Thread_Free( Thread_Control *the_thread )
_Objects_Get_information_id( the_thread->Object.id );
_User_extensions_Thread_delete( the_thread );
+ _User_extensions_Destroy_iterators( the_thread );
_ISR_lock_Destroy( &the_thread->Keys.Lock );
_Scheduler_Node_destroy( _Scheduler_Get( the_thread ), the_thread );
_ISR_lock_Destroy( &the_thread->Timer.Lock );
@@ -255,6 +256,7 @@ void _Thread_Life_action_handler(
executing->Life.state = THREAD_LIFE_NORMAL;
+ _User_extensions_Destroy_iterators( executing );
_Thread_Load_environment( executing );
_Thread_Restart_self( executing );
RTEMS_UNREACHABLE();
diff --git a/cpukit/score/src/userextaddset.c b/cpukit/score/src/userextaddset.c
index f34ad0072f..19bbd361e9 100644
--- a/cpukit/score/src/userextaddset.c
+++ b/cpukit/score/src/userextaddset.c
@@ -20,20 +20,20 @@
#endif
#include <rtems/score/userextimpl.h>
-#include <rtems/score/objectimpl.h>
#include <rtems/score/percpu.h>
-#include <rtems/score/sysstate.h>
void _User_extensions_Add_set(
User_extensions_Control *the_extension
)
{
- _Assert(
- _Objects_Allocator_is_owner()
- || _System_state_Is_before_multitasking( _System_state_Get() )
- );
+ ISR_lock_Context lock_context;
- _Chain_Append_unprotected( &_User_extensions_List, &the_extension->Node );
+ _User_extensions_Acquire( &lock_context );
+ _Chain_Append_unprotected(
+ &_User_extensions_List.Active,
+ &the_extension->Node
+ );
+ _User_extensions_Release( &lock_context );
/*
* If a switch handler is present, append it to the switch chain.
diff --git a/cpukit/score/src/userextiterate.c b/cpukit/score/src/userextiterate.c
index beeee9570b..6cb877452b 100644
--- a/cpukit/score/src/userextiterate.c
+++ b/cpukit/score/src/userextiterate.c
@@ -7,10 +7,10 @@
*/
/*
- * Copyright (c) 2012 embedded brains GmbH. All rights reserved.
+ * Copyright (c) 2012, 2016 embedded brains GmbH. All rights reserved.
*
* embedded brains GmbH
- * Obere Lagerstr. 30
+ * Dornierstr. 4
* 82178 Puchheim
* Germany
* <rtems@embedded-brains.de>
@@ -27,7 +27,16 @@
#include <rtems/config.h>
#include <rtems/score/userextimpl.h>
-CHAIN_DEFINE_EMPTY( _User_extensions_List );
+#include <pthread.h>
+
+User_extensions_List _User_extensions_List = {
+ CHAIN_INITIALIZER_EMPTY( _User_extensions_List.Active ),
+ CHAIN_ITERATOR_REGISTRY_INITIALIZER( _User_extensions_List.Iterators )
+#if defined(RTEMS_SMP)
+ ,
+ ISR_LOCK_INITIALIZER( "User Extensions List" )
+#endif
+};
void _User_extensions_Thread_create_visitor(
Thread_Control *executing,
@@ -139,17 +148,24 @@ void _User_extensions_Thread_terminate_visitor(
}
void _User_extensions_Iterate(
- void *arg,
- User_extensions_Visitor visitor
+ void *arg,
+ User_extensions_Visitor visitor,
+ Chain_Iterator_direction direction
)
{
- Thread_Control *executing = _Thread_Get_executing();
- const User_extensions_Table *callouts_current =
- rtems_configuration_get_user_extension_table();
- const User_extensions_Table *callouts_end =
- callouts_current + rtems_configuration_get_number_of_initial_extensions();
- const Chain_Node *node;
- const Chain_Node *tail;
+ Thread_Control *executing;
+ const User_extensions_Table *callouts_current;
+ const User_extensions_Table *callouts_end;
+ const Chain_Node *end;
+ Chain_Node *node;
+ User_extensions_Iterator iter;
+ ISR_lock_Context lock_context;
+
+ executing = _Thread_Get_executing();
+
+ callouts_current = rtems_configuration_get_user_extension_table();
+ callouts_end = callouts_current
+ + rtems_configuration_get_number_of_initial_extensions();
while ( callouts_current != callouts_end ) {
(*visitor)( executing, arg, callouts_current );
@@ -157,14 +173,44 @@ void _User_extensions_Iterate(
++callouts_current;
}
- node = _Chain_Immutable_first( &_User_extensions_List );
- tail = _Chain_Immutable_tail( &_User_extensions_List );
- while ( node != tail ) {
- const User_extensions_Control *extension =
- (const User_extensions_Control *) node;
+ if ( direction == CHAIN_ITERATOR_FORWARD ) {
+ end = _Chain_Immutable_tail( &_User_extensions_List.Active );
+ } else {
+ end = _Chain_Immutable_head( &_User_extensions_List.Active );
+ }
+
+ _User_extensions_Acquire( &lock_context );
+
+ _Chain_Iterator_initialize(
+ &_User_extensions_List.Active,
+ &_User_extensions_List.Iterators,
+ &iter.Iterator,
+ direction
+ );
+
+ if ( executing != NULL ) {
+ iter.previous = executing->last_user_extensions_iterator;
+ executing->last_user_extensions_iterator = &iter;
+ }
+
+ while ( ( node = _Chain_Iterator_next( &iter.Iterator ) ) != end ) {
+ const User_extensions_Control *extension;
+
+ _Chain_Iterator_set_position( &iter.Iterator, node );
+
+ _User_extensions_Release( &lock_context );
- (*visitor)( executing, arg, &extension->Callouts );
+ extension = (const User_extensions_Control *) node;
+ ( *visitor )( executing, arg, &extension->Callouts );
- node = _Chain_Immutable_next( node );
+ _User_extensions_Acquire( &lock_context );
}
+
+ if ( executing != NULL ) {
+ executing->last_user_extensions_iterator = iter.previous;
+ }
+
+ _Chain_Iterator_destroy( &iter.Iterator );
+
+ _User_extensions_Release( &lock_context );
}
diff --git a/cpukit/score/src/userextremoveset.c b/cpukit/score/src/userextremoveset.c
index 5b3fdd1aea..b25cc344b4 100644
--- a/cpukit/score/src/userextremoveset.c
+++ b/cpukit/score/src/userextremoveset.c
@@ -20,16 +20,21 @@
#endif
#include <rtems/score/userextimpl.h>
-#include <rtems/score/objectimpl.h>
#include <rtems/score/percpu.h>
void _User_extensions_Remove_set (
User_extensions_Control *the_extension
)
{
- _Assert( _Objects_Allocator_is_owner() );
+ ISR_lock_Context lock_context;
+ _User_extensions_Acquire( &lock_context );
+ _Chain_Iterator_registry_update(
+ &_User_extensions_List.Iterators,
+ &the_extension->Node
+ );
_Chain_Extract_unprotected( &the_extension->Node );
+ _User_extensions_Release( &lock_context );
/*
* If a switch handler is present, remove it.
diff --git a/testsuites/sptests/Makefile.am b/testsuites/sptests/Makefile.am
index f88e74af2d..ed8e759e87 100644
--- a/testsuites/sptests/Makefile.am
+++ b/testsuites/sptests/Makefile.am
@@ -33,6 +33,7 @@ _SUBDIRS = \
spsignal_err01 spport_err01 spmsgq_err01 spmsgq_err02 spsem_err01 \
spsem_err02 sptask_err01 spevent_err03 sptask_err03 sptask_err02 \
sptask_err04 spclock_err01
+_SUBDIRS += spextensions01
_SUBDIRS += spsysinit01
if HAS_SMP
else
diff --git a/testsuites/sptests/configure.ac b/testsuites/sptests/configure.ac
index 208940651c..f5481ae3bf 100644
--- a/testsuites/sptests/configure.ac
+++ b/testsuites/sptests/configure.ac
@@ -46,6 +46,7 @@ AM_CONDITIONAL(HAS_SMP,test "$rtems_cv_RTEMS_SMP" = "yes")
# Explicitly list all Makefiles here
AC_CONFIG_FILES([Makefile
+spextensions01/Makefile
sptimerserver01/Makefile
spsysinit01/Makefile
splinkersets01/Makefile
diff --git a/testsuites/sptests/spextensions01/Makefile.am b/testsuites/sptests/spextensions01/Makefile.am
new file mode 100644
index 0000000000..610c2b5deb
--- /dev/null
+++ b/testsuites/sptests/spextensions01/Makefile.am
@@ -0,0 +1,19 @@
+rtems_tests_PROGRAMS = spextensions01
+spextensions01_SOURCES = init.c
+
+dist_rtems_tests_DATA = spextensions01.scn spextensions01.doc
+
+include $(RTEMS_ROOT)/make/custom/@RTEMS_BSP@.cfg
+include $(top_srcdir)/../automake/compile.am
+include $(top_srcdir)/../automake/leaf.am
+
+AM_CPPFLAGS += -I$(top_srcdir)/../support/include
+
+LINK_OBJS = $(spextensions01_OBJECTS)
+LINK_LIBS = $(spextensions01_LDLIBS)
+
+spextensions01$(EXEEXT): $(spextensions01_OBJECTS) $(spextensions01_DEPENDENCIES)
+ @rm -f spextensions01$(EXEEXT)
+ $(make-exe)
+
+include $(top_srcdir)/../automake/local.am
diff --git a/testsuites/sptests/spextensions01/init.c b/testsuites/sptests/spextensions01/init.c
new file mode 100644
index 0000000000..d8593b9980
--- /dev/null
+++ b/testsuites/sptests/spextensions01/init.c
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2016 embedded brains GmbH. All rights reserved.
+ *
+ * embedded brains GmbH
+ * Dornierstr. 4
+ * 82178 Puchheim
+ * Germany
+ * <rtems@embedded-brains.de>
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution or at
+ * http://www.rtems.com/license/LICENSE.
+ */
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include <rtems/test.h>
+
+#include <bsp.h>
+
+const char rtems_test_name[] = "SPEXTENSIONS 1";
+
+static int counter;
+
+static int active_extensions = 2;
+
+static rtems_id master_task;
+
+static void assert_static_order(int index)
+{
+ assert((counter % active_extensions) == index);
+ ++counter;
+}
+
+static void assert_forward_order(int index)
+{
+ assert((counter % active_extensions) == index);
+ ++counter;
+}
+
+static void assert_reverse_order(int index)
+{
+ assert((counter % active_extensions) == (5 - index));
+ ++counter;
+}
+
+static bool zero_thread_create(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(0);
+ return true;
+}
+
+static void zero_thread_start(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(0);
+}
+
+static void zero_thread_restart(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(0);
+}
+
+static void zero_thread_delete(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(0);
+}
+
+static void zero_thread_switch(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(0);
+}
+
+static void zero_thread_begin(rtems_tcb *a)
+{
+ assert_static_order(0);
+}
+
+static void zero_thread_exitted(rtems_tcb *a)
+{
+ assert_static_order(0);
+}
+
+static void zero_fatal(
+ rtems_fatal_source source,
+ bool is_internal,
+ rtems_fatal_code code
+)
+{
+ if (source == RTEMS_FATAL_SOURCE_EXIT) {
+ assert_static_order(0);
+ }
+}
+
+static void zero_thread_terminate(rtems_tcb *a)
+{
+ assert_static_order(0);
+}
+
+static bool one_thread_create(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(1);
+ return true;
+}
+
+static void one_thread_start(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(1);
+}
+
+static void one_thread_restart(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(1);
+}
+
+static void one_thread_delete(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(1);
+}
+
+static void one_thread_switch(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(1);
+}
+
+static void one_thread_begin(rtems_tcb *a)
+{
+ assert_static_order(1);
+}
+
+static void one_thread_exitted(rtems_tcb *a)
+{
+ assert_static_order(1);
+}
+
+static void one_fatal(
+ rtems_fatal_source source,
+ bool is_internal,
+ rtems_fatal_code code
+)
+{
+ if (source == RTEMS_FATAL_SOURCE_EXIT) {
+ assert_static_order(1);
+ }
+}
+
+static void one_thread_terminate(rtems_tcb *a)
+{
+ assert_static_order(1);
+}
+
+static bool two_thread_create(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_forward_order(2);
+ return true;
+}
+
+static void two_thread_start(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_forward_order(2);
+}
+
+static void two_thread_restart(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(2);
+}
+
+static void two_thread_delete(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_reverse_order(2);
+}
+
+static void two_thread_switch(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_forward_order(2);
+}
+
+static void two_thread_begin(rtems_tcb *a)
+{
+ assert_forward_order(2);
+}
+
+static void two_thread_exitted(rtems_tcb *a)
+{
+ assert_forward_order(2);
+}
+
+static void two_fatal(
+ rtems_fatal_source source,
+ bool is_internal,
+ rtems_fatal_code code
+)
+{
+ if (source == RTEMS_FATAL_SOURCE_EXIT) {
+ assert_reverse_order(2);
+ assert(counter == 72);
+ rtems_test_endk();
+ }
+}
+
+static void two_thread_terminate(rtems_tcb *a)
+{
+ assert_forward_order(2);
+}
+
+static bool three_thread_create(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_forward_order(3);
+ return true;
+}
+
+static void three_thread_start(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_forward_order(3);
+}
+
+static void three_thread_restart(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_static_order(3);
+}
+
+static void three_thread_delete(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_reverse_order(3);
+}
+
+static void three_thread_switch(rtems_tcb *a, rtems_tcb *b)
+{
+ assert_forward_order(3);
+}
+
+static void three_thread_begin(rtems_tcb *a)
+{
+ assert_forward_order(3);
+}
+
+static void three_thread_exitted(rtems_tcb *a)
+{
+ assert_forward_order(3);
+}
+
+static void three_fatal(
+ rtems_fatal_source source,
+ bool is_internal,
+ rtems_fatal_code code
+)
+{
+ if (source == RTEMS_FATAL_SOURCE_EXIT) {
+ assert_reverse_order(3);
+ }
+}
+
+static void three_thread_terminate(rtems_tcb *a)
+{
+ assert_forward_order(3);
+}
+
+#define ZERO \
+ { \
+ .thread_create = zero_thread_create, \
+ .thread_start = zero_thread_start, \
+ .thread_restart = zero_thread_restart, \
+ .thread_delete = zero_thread_delete, \
+ .thread_switch = zero_thread_switch, \
+ .thread_begin = zero_thread_begin, \
+ .thread_exitted = zero_thread_exitted, \
+ .fatal = zero_fatal, \
+ .thread_terminate = zero_thread_terminate \
+ }
+
+#define ONE \
+ { \
+ .thread_create = one_thread_create, \
+ .thread_start = one_thread_start, \
+ .thread_restart = one_thread_restart, \
+ .thread_delete = one_thread_delete, \
+ .thread_switch = one_thread_switch, \
+ .thread_begin = one_thread_begin, \
+ .thread_exitted = one_thread_exitted, \
+ .fatal = one_fatal, \
+ .thread_terminate = one_thread_terminate \
+ }
+
+static const rtems_extensions_table two = {
+ .thread_create = two_thread_create,
+ .thread_start = two_thread_start,
+ .thread_restart = two_thread_restart,
+ .thread_delete = two_thread_delete,
+ .thread_switch = two_thread_switch,
+ .thread_begin = two_thread_begin,
+ .thread_exitted = two_thread_exitted,
+ .fatal = two_fatal,
+ .thread_terminate = two_thread_terminate
+};
+
+static const rtems_extensions_table three = {
+ .thread_create = three_thread_create,
+ .thread_start = three_thread_start,
+ .thread_restart = three_thread_restart,
+ .thread_delete = three_thread_delete,
+ .thread_switch = three_thread_switch,
+ .thread_begin = three_thread_begin,
+ .thread_exitted = three_thread_exitted,
+ .fatal = three_fatal,
+ .thread_terminate = three_thread_terminate
+};
+
+static const rtems_extensions_table initial_test =
+ RTEMS_TEST_INITIAL_EXTENSION;
+
+#ifdef BSP_INITIAL_EXTENSION
+static const rtems_extensions_table initial_bsp =
+ BSP_INITIAL_EXTENSION;
+#endif
+
+static void wake_up_master(void)
+{
+ rtems_status_code sc;
+
+ sc = rtems_event_transient_send(master_task);
+ assert(sc == RTEMS_SUCCESSFUL);
+}
+
+static void wait_for_worker(void)
+{
+ rtems_status_code sc;
+
+ sc = rtems_event_transient_receive(
+ RTEMS_WAIT,
+ RTEMS_NO_TIMEOUT
+ );
+ assert(sc == RTEMS_SUCCESSFUL);
+}
+
+static void worker(rtems_task_argument arg)
+{
+ wake_up_master();
+
+ (void) rtems_task_suspend(RTEMS_SELF);
+ assert(false);
+}
+
+static void test(void)
+{
+ rtems_status_code sc;
+ rtems_id id;
+
+ master_task = rtems_task_self();
+
+#ifdef BSP_INITIAL_EXTENSION
+ sc = rtems_extension_create(
+ rtems_build_name(' ', 'B', 'S', 'P'),
+ &initial_bsp,
+ &id
+ );
+ assert(sc == RTEMS_SUCCESSFUL);
+#undef BSP_INITIAL_EXTENSION
+#endif
+
+ sc = rtems_extension_create(
+ rtems_build_name('T', 'E', 'S', 'T'),
+ &initial_test,
+ &id
+ );
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ sc = rtems_extension_create(
+ rtems_build_name('2', ' ', ' ', ' '),
+ &two,
+ &id
+ );
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ sc = rtems_extension_create(
+ rtems_build_name('3', ' ', ' ', ' '),
+ &three,
+ &id
+ );
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ active_extensions = 4;
+ assert(counter == 14);
+ counter = 16;
+
+ sc = rtems_task_create(
+ rtems_build_name('W', 'O', 'R', 'K'),
+ 2,
+ RTEMS_MINIMUM_STACK_SIZE,
+ RTEMS_DEFAULT_MODES,
+ RTEMS_DEFAULT_ATTRIBUTES,
+ &id
+ );
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ sc = rtems_task_start(id, worker, 0);
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ wait_for_worker();
+
+ sc = rtems_task_restart(id, 0);
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ wait_for_worker();
+
+ sc = rtems_task_delete(id);
+ assert(sc == RTEMS_SUCCESSFUL);
+
+ /* Process zombies to trigger delete extensions */
+ sc = rtems_task_create(
+ rtems_build_name('N', 'U', 'L', 'L'),
+ 2,
+ SIZE_MAX,
+ RTEMS_DEFAULT_MODES,
+ RTEMS_DEFAULT_ATTRIBUTES,
+ &id
+ );
+ assert(sc == RTEMS_UNSATISFIED);
+}
+
+static void Init(rtems_task_argument arg)
+{
+ rtems_test_begink();
+
+ test();
+
+ exit(0);
+}
+
+#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
+#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER
+
+#define CONFIGURE_MAXIMUM_USER_EXTENSIONS 4
+
+#define CONFIGURE_MAXIMUM_TASKS 2
+
+#define CONFIGURE_INITIAL_EXTENSIONS ZERO, ONE
+
+#define CONFIGURE_RTEMS_INIT_TASKS_TABLE
+
+#define CONFIGURE_INIT
+
+#include <rtems/confdefs.h>
diff --git a/testsuites/sptests/spextensions01/spextensions01.doc b/testsuites/sptests/spextensions01/spextensions01.doc
new file mode 100644
index 0000000000..5e91e8400a
--- /dev/null
+++ b/testsuites/sptests/spextensions01/spextensions01.doc
@@ -0,0 +1,12 @@
+This file describes the directives and concepts tested by this test set.
+
+test set name: spextensions01
+
+directives:
+
+ - rtems_extension_create()
+ - _User_extensions_Iterate()
+
+concepts:
+
+ - Ensure that the user extensions are called in the right order.
diff --git a/testsuites/sptests/spextensions01/spextensions01.scn b/testsuites/sptests/spextensions01/spextensions01.scn
new file mode 100644
index 0000000000..7a51b9441c
--- /dev/null
+++ b/testsuites/sptests/spextensions01/spextensions01.scn
@@ -0,0 +1,2 @@
+*** BEGIN OF TEST SPEXTENSIONS 1 ***
+*** END OF TEST SPEXTENSIONS 1 ***