Split code at the end of the smpthreadpin01 test so that it could be reused for the smpthreadpin02 test. --- spec/build/testsuites/smptests/grp.yml | 2 + spec/build/testsuites/smptests/smpthreadpin01.yml | 2 +- spec/build/testsuites/smptests/smpthreadpin02.yml | 20 + testsuites/smptests/include/smpthreadpin.h | 593 +++++++++++++++++++++ testsuites/smptests/smpthreadpin01/init.c | 581 +------------------- testsuites/smptests/smpthreadpin02/init.c | 56 ++ .../smptests/smpthreadpin02/smpthreadpin02.doc | 12 + .../smptests/smpthreadpin02/smpthreadpin02.scn | 35 ++ 8 files changed, 721 insertions(+), 580 deletions(-) create mode 100644 spec/build/testsuites/smptests/smpthreadpin02.yml create mode 100644 testsuites/smptests/include/smpthreadpin.h create mode 100644 testsuites/smptests/smpthreadpin02/init.c create mode 100644 testsuites/smptests/smpthreadpin02/smpthreadpin02.doc create mode 100644 testsuites/smptests/smpthreadpin02/smpthreadpin02.scn
diff --git a/spec/build/testsuites/smptests/grp.yml b/spec/build/testsuites/smptests/grp.yml index d6a1bf5..7b8cb62 100644 --- a/spec/build/testsuites/smptests/grp.yml +++ b/spec/build/testsuites/smptests/grp.yml @@ -133,6 +133,8 @@ links: - role: build-dependency uid: smpthreadpin01 - role: build-dependency + uid: smpthreadpin02 +- role: build-dependency uid: smpunsupported01 - role: build-dependency uid: smpwakeafter01 diff --git a/spec/build/testsuites/smptests/smpthreadpin01.yml b/spec/build/testsuites/smptests/smpthreadpin01.yml index 9de0166..21f549d 100644 --- a/spec/build/testsuites/smptests/smpthreadpin01.yml +++ b/spec/build/testsuites/smptests/smpthreadpin01.yml @@ -8,7 +8,7 @@ cxxflags: [] enabled-by: - RTEMS_SMP features: c cprogram -includes: [] +includes: [testsuites/smptests/include] ldflags: [] links: [] source: diff --git a/spec/build/testsuites/smptests/smpthreadpin02.yml b/spec/build/testsuites/smptests/smpthreadpin02.yml new file mode 100644 index 0000000..0f07ca4 --- /dev/null +++ b/spec/build/testsuites/smptests/smpthreadpin02.yml @@ -0,0 +1,20 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2021 On-Line Applications Research Corporation (OAR). +cppflags: [] +cxxflags: [] +enabled-by: +- RTEMS_SMP +features: c cprogram +includes: [testsuites/smptests/include] +ldflags: [] +links: [] +source: +- testsuites/smptests/smpthreadpin02/init.c +stlib: [] +target: testsuites/smptests/smpthreadpin02.exe +type: build +use-after: [] +use-before: [] diff --git a/testsuites/smptests/include/smpthreadpin.h b/testsuites/smptests/include/smpthreadpin.h new file mode 100644 index 0000000..75d24d8 --- /dev/null +++ b/testsuites/smptests/include/smpthreadpin.h @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2018 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <rt...@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.org/license/LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <rtems.h> +#include <rtems/thread.h> +#include <rtems/score/threadimpl.h> + +#include <tmacros.h> + +#define CPU_COUNT 2 + +#define SCHED_A rtems_build_name(' ', ' ', ' ', 'A') + +#define SCHED_B rtems_build_name(' ', ' ', ' ', 'B') + +#define EVENT_WAKEUP_MASTER RTEMS_EVENT_0 + +#define EVENT_MTX_LOCK RTEMS_EVENT_1 + +#define EVENT_MTX_UNLOCK RTEMS_EVENT_2 + +#define EVENT_MOVE_BUSY_TO_CPU_0 RTEMS_EVENT_3 + +#define EVENT_MOVE_BUSY_TO_CPU_1 RTEMS_EVENT_4 + +#define EVENT_MOVE_SELF_TO_CPU_0 RTEMS_EVENT_5 + +#define EVENT_MOVE_SELF_TO_CPU_1 RTEMS_EVENT_6 + +#define EVENT_SET_SELF_PRIO_TO_LOW RTEMS_EVENT_7 + +#define EVENT_SET_BUSY_PRIO_TO_IDLE RTEMS_EVENT_8 + +#define EVENT_SET_FLAG RTEMS_EVENT_9 + +#define PRIO_IDLE 6 + +#define PRIO_VERY_LOW 5 + +#define PRIO_LOW 4 + +#define PRIO_MIDDLE 3 + +#define PRIO_HIGH 2 + +#define PRIO_VERY_HIGH 1 + +typedef struct { + rtems_id master; + rtems_id event; + rtems_id event_2; + rtems_id busy; + rtems_id sched_a; + rtems_id sched_b; + rtems_mutex mtx; + volatile bool flag; +} test_context; + +static test_context test_instance; + +static rtems_task_priority set_prio(rtems_id id, rtems_task_priority prio) +{ + rtems_status_code sc; + rtems_task_priority old_prio; + + old_prio = 0xffffffff; + sc = rtems_task_set_priority(id, prio, &old_prio); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + return old_prio; +} + +static void set_affinity(rtems_id task, uint32_t cpu_index) +{ + rtems_status_code sc; + rtems_id sched_cpu; + rtems_id sched_task; + cpu_set_t set; + + sc = rtems_scheduler_ident_by_processor(cpu_index, &sched_cpu); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_task_get_scheduler(task, &sched_task); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + if (sched_task != sched_cpu) { + rtems_task_priority prio; + + CPU_FILL(&set); + sc = rtems_task_set_affinity(task, sizeof(set), &set); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + prio = set_prio(task, RTEMS_CURRENT_PRIORITY); + sc = rtems_task_set_scheduler(task, sched_cpu, prio); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + } + + CPU_ZERO(&set); + CPU_SET((int) cpu_index, &set); + sc = rtems_task_set_affinity(task, sizeof(set), &set); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static void send_events(rtems_id task, rtems_event_set events) +{ + rtems_status_code sc; + + sc = rtems_event_send(task, events); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static rtems_event_set wait_for_events(void) +{ + rtems_event_set events; + rtems_status_code sc; + + sc = rtems_event_receive( + RTEMS_ALL_EVENTS, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events + ); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + return events; +} + +static void pin(bool blocked) +{ + Per_CPU_Control *cpu_self; + Thread_Control *executing; + + cpu_self = _Thread_Dispatch_disable(); + executing = _Per_CPU_Get_executing(cpu_self); + + if (blocked) { + _Thread_Set_state(executing, STATES_SUSPENDED); + } + + _Thread_Pin(executing); + + if (blocked) { + _Thread_Clear_state(executing, STATES_SUSPENDED); + } + + _Thread_Dispatch_enable(cpu_self); +} + +static void unpin(bool blocked) +{ + Per_CPU_Control *cpu_self; + Thread_Control *executing; + + cpu_self = _Thread_Dispatch_disable(); + executing = _Per_CPU_Get_executing(cpu_self); + + if (blocked) { + _Thread_Set_state(executing, STATES_SUSPENDED); + } + + _Thread_Unpin(executing, cpu_self); + + if (blocked) { + _Thread_Clear_state(executing, STATES_SUSPENDED); + } + + _Thread_Dispatch_enable(cpu_self); +} + +static void event_task(rtems_task_argument arg) +{ + test_context *ctx; + + ctx = (test_context *) arg; + + while (true) { + rtems_event_set events; + + events = wait_for_events(); + + /* + * The order of event processing is important! + */ + + if ((events & EVENT_MTX_LOCK) != 0) { + rtems_mutex_lock(&ctx->mtx); + } + + if ((events & EVENT_MTX_UNLOCK) != 0) { + rtems_mutex_unlock(&ctx->mtx); + } + + if ((events & EVENT_MOVE_BUSY_TO_CPU_0) != 0) { + set_affinity(ctx->busy, 0); + } + + if ((events & EVENT_MOVE_BUSY_TO_CPU_1) != 0) { + set_affinity(ctx->busy, 1); + } + + if ((events & EVENT_MOVE_SELF_TO_CPU_0) != 0) { + set_affinity(RTEMS_SELF, 0); + } + + if ((events & EVENT_MOVE_SELF_TO_CPU_1) != 0) { + set_affinity(RTEMS_SELF, 1); + } + + if ((events & EVENT_SET_SELF_PRIO_TO_LOW) != 0) { + set_prio(RTEMS_SELF, PRIO_LOW); + } + + if ((events & EVENT_SET_BUSY_PRIO_TO_IDLE) != 0) { + set_prio(ctx->busy, PRIO_IDLE); + } + + if ((events & EVENT_SET_FLAG) != 0) { + ctx->flag = true; + } + + if ((events & EVENT_WAKEUP_MASTER) != 0) { + send_events(ctx->master, EVENT_WAKEUP_MASTER); + } + } +} + +static void busy_task(rtems_task_argument arg) +{ + (void) arg; + + _CPU_Thread_Idle_body(0); +} + +static const char *blocked_or_ready(bool blocked) +{ + return blocked ? "blocked" : "ready"; +} + +static void reconfigure_scheduler(test_context *ctx) +{ + rtems_status_code sc; + + puts("reconfigure scheduler"); + + set_prio(ctx->master, PRIO_MIDDLE); + set_prio(ctx->event, PRIO_LOW); + set_prio(ctx->event_2, PRIO_VERY_LOW); + set_prio(ctx->busy, PRIO_IDLE); + + set_affinity(ctx->master, 0); + set_affinity(ctx->event, 0); + set_affinity(ctx->event_2, 0); + set_affinity(ctx->busy, 0); + + sc = rtems_scheduler_remove_processor(ctx->sched_a, 1); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_scheduler_add_processor(ctx->sched_b, 1); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); +} + +static void test_simple_pin_unpin(test_context *ctx, int run) +{ + Per_CPU_Control *cpu_self; + Thread_Control *executing; + + printf("test simple wait unpin (run %i)\n", run); + + set_affinity(ctx->busy, 0); + set_prio(ctx->busy, PRIO_IDLE); + set_prio(RTEMS_SELF, PRIO_MIDDLE); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + cpu_self = _Thread_Dispatch_disable(); + executing = _Per_CPU_Get_executing(cpu_self); + _Thread_Pin(executing); + + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + _Thread_Unpin(executing, cpu_self); + _Thread_Dispatch_enable(cpu_self); + + rtems_test_assert(rtems_scheduler_get_processor() == 1); +} + +static void test_pin_wait_unpin(test_context *ctx, bool blocked, int run) +{ + printf("test pin wait unpin (%s, run %i)\n", blocked_or_ready(blocked), run); + + set_affinity(ctx->busy, 0); + set_prio(ctx->busy, PRIO_IDLE); + set_prio(RTEMS_SELF, PRIO_MIDDLE); + set_prio(ctx->event, PRIO_LOW); + set_affinity(ctx->event, 1); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + pin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + send_events(ctx->event, EVENT_WAKEUP_MASTER); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + wait_for_events(); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + set_prio(ctx->busy, PRIO_HIGH); + set_affinity(ctx->busy, 0); + unpin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 1); +} + +static void test_pin_preempt_unpin(test_context *ctx, bool blocked, int run) +{ + printf( + "test pin preempt unpin (%s, run %i)\n", + blocked_or_ready(blocked), + run + ); + + set_prio(RTEMS_SELF, PRIO_MIDDLE); + set_prio(ctx->event, PRIO_VERY_HIGH); + set_prio(ctx->busy, PRIO_HIGH); + set_affinity(ctx->event, 0); + set_affinity(ctx->busy, 0); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + pin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + ctx->flag = false; + send_events( + ctx->event, + EVENT_MOVE_BUSY_TO_CPU_1 | EVENT_SET_SELF_PRIO_TO_LOW + | EVENT_SET_BUSY_PRIO_TO_IDLE | EVENT_SET_FLAG + ); + + while (!ctx->flag) { + rtems_test_assert(rtems_scheduler_get_processor() == 1); + } + + set_affinity(ctx->busy, 0); + unpin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 1); +} + +static void test_pin_home_no_help_unpin( + test_context *ctx, + bool blocked, + int run +) +{ + rtems_status_code sc; + + printf( + "test pin home no help unpin (%s, run %i)\n", + blocked_or_ready(blocked), + run + ); + + set_affinity(ctx->busy, 1); + set_prio(ctx->busy, PRIO_IDLE); + set_prio(RTEMS_SELF, PRIO_MIDDLE); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + pin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + sc = rtems_task_set_scheduler(RTEMS_SELF, ctx->sched_b, 1); + rtems_test_assert(sc == RTEMS_RESOURCE_IN_USE); + + rtems_mutex_lock(&ctx->mtx); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + set_affinity(ctx->event, 1); + set_prio(ctx->event, PRIO_MIDDLE); + + send_events(ctx->event, EVENT_MTX_LOCK); + set_prio(ctx->event_2, PRIO_LOW); + set_affinity(ctx->event_2, 1); + send_events(ctx->event_2, EVENT_WAKEUP_MASTER); + wait_for_events(); + + /* Now the event task can help us */ + rtems_test_assert(ctx->mtx._Queue._heads != NULL); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + set_affinity(ctx->event_2, 0); + set_affinity(ctx->busy, 1); + set_prio(ctx->busy, PRIO_HIGH); + send_events( + ctx->event_2, + EVENT_MOVE_BUSY_TO_CPU_0 | EVENT_MOVE_SELF_TO_CPU_1 + | EVENT_SET_SELF_PRIO_TO_LOW | EVENT_SET_BUSY_PRIO_TO_IDLE + ); + set_prio(ctx->event_2, PRIO_VERY_HIGH); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + rtems_mutex_unlock(&ctx->mtx); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + send_events(ctx->event, EVENT_WAKEUP_MASTER | EVENT_MTX_UNLOCK); + wait_for_events(); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + unpin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 0); +} + +static void test_pin_foreign_no_help_unpin( + test_context *ctx, + bool blocked, + int run +) +{ + printf( + "test pin foreign no help unpin (%s, run %i)\n", + blocked_or_ready(blocked), + run + ); + + set_affinity(ctx->busy, 1); + set_prio(ctx->busy, PRIO_IDLE); + set_prio(RTEMS_SELF, PRIO_MIDDLE); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + rtems_mutex_lock(&ctx->mtx); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + set_affinity(ctx->event, 1); + set_prio(ctx->event, PRIO_MIDDLE); + send_events(ctx->event, EVENT_MTX_LOCK); + set_prio(ctx->event_2, PRIO_LOW); + set_affinity(ctx->event_2, 1); + send_events(ctx->event_2, EVENT_WAKEUP_MASTER); + wait_for_events(); + + /* Now the event task can help us */ + rtems_test_assert(ctx->mtx._Queue._heads != NULL); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + /* Request help */ + set_affinity(ctx->busy, 0); + set_prio(ctx->busy, PRIO_HIGH); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + /* Pin while using foreign scheduler */ + pin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + set_affinity(ctx->event_2, 1); + send_events( + ctx->event_2, + EVENT_MOVE_BUSY_TO_CPU_1 | EVENT_MOVE_SELF_TO_CPU_0 + | EVENT_SET_SELF_PRIO_TO_LOW | EVENT_SET_BUSY_PRIO_TO_IDLE + ); + set_prio(ctx->event_2, PRIO_VERY_HIGH); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + unpin(blocked); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + set_prio(ctx->busy, PRIO_IDLE); + rtems_mutex_unlock(&ctx->mtx); + rtems_test_assert(rtems_scheduler_get_processor() == 0); + + send_events(ctx->event, EVENT_WAKEUP_MASTER | EVENT_MTX_UNLOCK); + wait_for_events(); + rtems_test_assert(rtems_scheduler_get_processor() == 0); +} + +static void test(test_context *ctx) +{ + rtems_status_code sc; + int run; + + ctx->master = rtems_task_self(); + + rtems_mutex_init(&ctx->mtx, "test"); + + sc = rtems_scheduler_ident(SCHED_A, &ctx->sched_a); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_scheduler_ident(SCHED_B, &ctx->sched_b); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_task_create( + rtems_build_name('B', 'U', 'S', 'Y'), + PRIO_HIGH, + RTEMS_MINIMUM_STACK_SIZE, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES, + &ctx->busy + ); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_task_start(ctx->busy, busy_task, (rtems_task_argument) ctx); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + set_affinity(ctx->busy, 0); + set_prio(ctx->busy, PRIO_IDLE); + rtems_test_assert(rtems_scheduler_get_processor() == 1); + + sc = rtems_task_create( + rtems_build_name('E', 'V', 'T', '1'), + PRIO_LOW, + RTEMS_MINIMUM_STACK_SIZE, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES, + &ctx->event + ); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_task_start(ctx->event, event_task, (rtems_task_argument) ctx); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + send_events(ctx->event, EVENT_WAKEUP_MASTER); + wait_for_events(); + + sc = rtems_task_create( + rtems_build_name('E', 'V', 'T', '2'), + PRIO_LOW, + RTEMS_MINIMUM_STACK_SIZE, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES, + &ctx->event_2 + ); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_task_start(ctx->event_2, event_task, (rtems_task_argument) ctx); + rtems_test_assert(sc == RTEMS_SUCCESSFUL); + + send_events(ctx->event_2, EVENT_WAKEUP_MASTER); + wait_for_events(); + + for (run = 1; run <= 3; ++run) { + test_simple_pin_unpin(ctx, run); + test_pin_wait_unpin(ctx, true, run); + test_pin_wait_unpin(ctx, false, run); + test_pin_preempt_unpin(ctx, true, run); + test_pin_preempt_unpin(ctx, false, run); + } + + reconfigure_scheduler(ctx); + + for (run = 1; run <= 3; ++run) { + test_pin_home_no_help_unpin(ctx, true, run); + test_pin_home_no_help_unpin(ctx, false, run); + test_pin_foreign_no_help_unpin(ctx, true, run); + test_pin_foreign_no_help_unpin(ctx, false, run); + } +} + +static void Init(rtems_task_argument arg) +{ + TEST_BEGIN(); + + if (rtems_scheduler_get_processor_maximum() == CPU_COUNT) { + test(&test_instance); + } else { + puts("warning: wrong processor count to run the test"); + } + + TEST_END(); + rtems_test_exit(0); +} + +#define CONFIGURE_APPLICATION_DOES_NOT_NEED_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER + +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION + +#define CONFIGURE_MAXIMUM_PROCESSORS CPU_COUNT + +#define CONFIGURE_MAXIMUM_TASKS 4 + +#define CONFIGURE_INIT_TASK_PRIORITY PRIO_MIDDLE + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + diff --git a/testsuites/smptests/smpthreadpin01/init.c b/testsuites/smptests/smpthreadpin01/init.c index 7c3c9b2..7684159 100644 --- a/testsuites/smptests/smpthreadpin01/init.c +++ b/testsuites/smptests/smpthreadpin01/init.c @@ -12,587 +12,10 @@ * http://www.rtems.org/license/LICENSE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include <rtems.h> -#include <rtems/thread.h> -#include <rtems/score/threadimpl.h> - -#include <tmacros.h> +#include <smpthreadpin.h> const char rtems_test_name[] = "SMPTHREADPIN 1"; -#define CPU_COUNT 2 - -#define SCHED_A rtems_build_name(' ', ' ', ' ', 'A') - -#define SCHED_B rtems_build_name(' ', ' ', ' ', 'B') - -#define EVENT_WAKEUP_MASTER RTEMS_EVENT_0 - -#define EVENT_MTX_LOCK RTEMS_EVENT_1 - -#define EVENT_MTX_UNLOCK RTEMS_EVENT_2 - -#define EVENT_MOVE_BUSY_TO_CPU_0 RTEMS_EVENT_3 - -#define EVENT_MOVE_BUSY_TO_CPU_1 RTEMS_EVENT_4 - -#define EVENT_MOVE_SELF_TO_CPU_0 RTEMS_EVENT_5 - -#define EVENT_MOVE_SELF_TO_CPU_1 RTEMS_EVENT_6 - -#define EVENT_SET_SELF_PRIO_TO_LOW RTEMS_EVENT_7 - -#define EVENT_SET_BUSY_PRIO_TO_IDLE RTEMS_EVENT_8 - -#define EVENT_SET_FLAG RTEMS_EVENT_9 - -#define PRIO_IDLE 6 - -#define PRIO_VERY_LOW 5 - -#define PRIO_LOW 4 - -#define PRIO_MIDDLE 3 - -#define PRIO_HIGH 2 - -#define PRIO_VERY_HIGH 1 - -typedef struct { - rtems_id master; - rtems_id event; - rtems_id event_2; - rtems_id busy; - rtems_id sched_a; - rtems_id sched_b; - rtems_mutex mtx; - volatile bool flag; -} test_context; - -static test_context test_instance; - -static rtems_task_priority set_prio(rtems_id id, rtems_task_priority prio) -{ - rtems_status_code sc; - rtems_task_priority old_prio; - - old_prio = 0xffffffff; - sc = rtems_task_set_priority(id, prio, &old_prio); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - return old_prio; -} - -static void set_affinity(rtems_id task, uint32_t cpu_index) -{ - rtems_status_code sc; - rtems_id sched_cpu; - rtems_id sched_task; - cpu_set_t set; - - sc = rtems_scheduler_ident_by_processor(cpu_index, &sched_cpu); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - sc = rtems_task_get_scheduler(task, &sched_task); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - if (sched_task != sched_cpu) { - rtems_task_priority prio; - - CPU_FILL(&set); - sc = rtems_task_set_affinity(task, sizeof(set), &set); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - prio = set_prio(task, RTEMS_CURRENT_PRIORITY); - sc = rtems_task_set_scheduler(task, sched_cpu, prio); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - } - - CPU_ZERO(&set); - CPU_SET((int) cpu_index, &set); - sc = rtems_task_set_affinity(task, sizeof(set), &set); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); -} - -static void send_events(rtems_id task, rtems_event_set events) -{ - rtems_status_code sc; - - sc = rtems_event_send(task, events); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); -} - -static rtems_event_set wait_for_events(void) -{ - rtems_event_set events; - rtems_status_code sc; - - sc = rtems_event_receive( - RTEMS_ALL_EVENTS, - RTEMS_EVENT_ANY | RTEMS_WAIT, - RTEMS_NO_TIMEOUT, - &events - ); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - return events; -} - -static void pin(bool blocked) -{ - Per_CPU_Control *cpu_self; - Thread_Control *executing; - - cpu_self = _Thread_Dispatch_disable(); - executing = _Per_CPU_Get_executing(cpu_self); - - if (blocked) { - _Thread_Set_state(executing, STATES_SUSPENDED); - } - - _Thread_Pin(executing); - - if (blocked) { - _Thread_Clear_state(executing, STATES_SUSPENDED); - } - - _Thread_Dispatch_enable(cpu_self); -} - -static void unpin(bool blocked) -{ - Per_CPU_Control *cpu_self; - Thread_Control *executing; - - cpu_self = _Thread_Dispatch_disable(); - executing = _Per_CPU_Get_executing(cpu_self); - - if (blocked) { - _Thread_Set_state(executing, STATES_SUSPENDED); - } - - _Thread_Unpin(executing, cpu_self); - - if (blocked) { - _Thread_Clear_state(executing, STATES_SUSPENDED); - } - - _Thread_Dispatch_enable(cpu_self); -} - -static void event_task(rtems_task_argument arg) -{ - test_context *ctx; - - ctx = (test_context *) arg; - - while (true) { - rtems_event_set events; - - events = wait_for_events(); - - /* - * The order of event processing is important! - */ - - if ((events & EVENT_MTX_LOCK) != 0) { - rtems_mutex_lock(&ctx->mtx); - } - - if ((events & EVENT_MTX_UNLOCK) != 0) { - rtems_mutex_unlock(&ctx->mtx); - } - - if ((events & EVENT_MOVE_BUSY_TO_CPU_0) != 0) { - set_affinity(ctx->busy, 0); - } - - if ((events & EVENT_MOVE_BUSY_TO_CPU_1) != 0) { - set_affinity(ctx->busy, 1); - } - - if ((events & EVENT_MOVE_SELF_TO_CPU_0) != 0) { - set_affinity(RTEMS_SELF, 0); - } - - if ((events & EVENT_MOVE_SELF_TO_CPU_1) != 0) { - set_affinity(RTEMS_SELF, 1); - } - - if ((events & EVENT_SET_SELF_PRIO_TO_LOW) != 0) { - set_prio(RTEMS_SELF, PRIO_LOW); - } - - if ((events & EVENT_SET_BUSY_PRIO_TO_IDLE) != 0) { - set_prio(ctx->busy, PRIO_IDLE); - } - - if ((events & EVENT_SET_FLAG) != 0) { - ctx->flag = true; - } - - if ((events & EVENT_WAKEUP_MASTER) != 0) { - send_events(ctx->master, EVENT_WAKEUP_MASTER); - } - } -} - -static void busy_task(rtems_task_argument arg) -{ - (void) arg; - - _CPU_Thread_Idle_body(0); -} - -static const char *blocked_or_ready(bool blocked) -{ - return blocked ? "blocked" : "ready"; -} - -static void reconfigure_scheduler(test_context *ctx) -{ - rtems_status_code sc; - - puts("reconfigure scheduler"); - - set_prio(ctx->master, PRIO_MIDDLE); - set_prio(ctx->event, PRIO_LOW); - set_prio(ctx->event_2, PRIO_VERY_LOW); - set_prio(ctx->busy, PRIO_IDLE); - - set_affinity(ctx->master, 0); - set_affinity(ctx->event, 0); - set_affinity(ctx->event_2, 0); - set_affinity(ctx->busy, 0); - - sc = rtems_scheduler_remove_processor(ctx->sched_a, 1); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - sc = rtems_scheduler_add_processor(ctx->sched_b, 1); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); -} - -static void test_simple_pin_unpin(test_context *ctx, int run) -{ - Per_CPU_Control *cpu_self; - Thread_Control *executing; - - printf("test simple wait unpin (run %i)\n", run); - - set_affinity(ctx->busy, 0); - set_prio(ctx->busy, PRIO_IDLE); - set_prio(RTEMS_SELF, PRIO_MIDDLE); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - cpu_self = _Thread_Dispatch_disable(); - executing = _Per_CPU_Get_executing(cpu_self); - _Thread_Pin(executing); - - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - _Thread_Unpin(executing, cpu_self); - _Thread_Dispatch_enable(cpu_self); - - rtems_test_assert(rtems_scheduler_get_processor() == 1); -} - -static void test_pin_wait_unpin(test_context *ctx, bool blocked, int run) -{ - printf("test pin wait unpin (%s, run %i)\n", blocked_or_ready(blocked), run); - - set_affinity(ctx->busy, 0); - set_prio(ctx->busy, PRIO_IDLE); - set_prio(RTEMS_SELF, PRIO_MIDDLE); - set_prio(ctx->event, PRIO_LOW); - set_affinity(ctx->event, 1); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - pin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - send_events(ctx->event, EVENT_WAKEUP_MASTER); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - wait_for_events(); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - set_prio(ctx->busy, PRIO_HIGH); - set_affinity(ctx->busy, 0); - unpin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 1); -} - -static void test_pin_preempt_unpin(test_context *ctx, bool blocked, int run) -{ - printf( - "test pin preempt unpin (%s, run %i)\n", - blocked_or_ready(blocked), - run - ); - - set_prio(RTEMS_SELF, PRIO_MIDDLE); - set_prio(ctx->event, PRIO_VERY_HIGH); - set_prio(ctx->busy, PRIO_HIGH); - set_affinity(ctx->event, 0); - set_affinity(ctx->busy, 0); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - pin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - ctx->flag = false; - send_events( - ctx->event, - EVENT_MOVE_BUSY_TO_CPU_1 | EVENT_SET_SELF_PRIO_TO_LOW - | EVENT_SET_BUSY_PRIO_TO_IDLE | EVENT_SET_FLAG - ); - - while (!ctx->flag) { - rtems_test_assert(rtems_scheduler_get_processor() == 1); - } - - set_affinity(ctx->busy, 0); - unpin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 1); -} - -static void test_pin_home_no_help_unpin( - test_context *ctx, - bool blocked, - int run -) -{ - rtems_status_code sc; - - printf( - "test pin home no help unpin (%s, run %i)\n", - blocked_or_ready(blocked), - run - ); - - set_affinity(ctx->busy, 1); - set_prio(ctx->busy, PRIO_IDLE); - set_prio(RTEMS_SELF, PRIO_MIDDLE); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - pin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - sc = rtems_task_set_scheduler(RTEMS_SELF, ctx->sched_b, 1); - rtems_test_assert(sc == RTEMS_RESOURCE_IN_USE); - - rtems_mutex_lock(&ctx->mtx); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - set_affinity(ctx->event, 1); - set_prio(ctx->event, PRIO_MIDDLE); - - send_events(ctx->event, EVENT_MTX_LOCK); - set_prio(ctx->event_2, PRIO_LOW); - set_affinity(ctx->event_2, 1); - send_events(ctx->event_2, EVENT_WAKEUP_MASTER); - wait_for_events(); - - /* Now the event task can help us */ - rtems_test_assert(ctx->mtx._Queue._heads != NULL); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - set_affinity(ctx->event_2, 0); - set_affinity(ctx->busy, 1); - set_prio(ctx->busy, PRIO_HIGH); - send_events( - ctx->event_2, - EVENT_MOVE_BUSY_TO_CPU_0 | EVENT_MOVE_SELF_TO_CPU_1 - | EVENT_SET_SELF_PRIO_TO_LOW | EVENT_SET_BUSY_PRIO_TO_IDLE - ); - set_prio(ctx->event_2, PRIO_VERY_HIGH); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - rtems_mutex_unlock(&ctx->mtx); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - send_events(ctx->event, EVENT_WAKEUP_MASTER | EVENT_MTX_UNLOCK); - wait_for_events(); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - unpin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 0); -} - -static void test_pin_foreign_no_help_unpin( - test_context *ctx, - bool blocked, - int run -) -{ - printf( - "test pin foreign no help unpin (%s, run %i)\n", - blocked_or_ready(blocked), - run - ); - - set_affinity(ctx->busy, 1); - set_prio(ctx->busy, PRIO_IDLE); - set_prio(RTEMS_SELF, PRIO_MIDDLE); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - rtems_mutex_lock(&ctx->mtx); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - set_affinity(ctx->event, 1); - set_prio(ctx->event, PRIO_MIDDLE); - send_events(ctx->event, EVENT_MTX_LOCK); - set_prio(ctx->event_2, PRIO_LOW); - set_affinity(ctx->event_2, 1); - send_events(ctx->event_2, EVENT_WAKEUP_MASTER); - wait_for_events(); - - /* Now the event task can help us */ - rtems_test_assert(ctx->mtx._Queue._heads != NULL); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - /* Request help */ - set_affinity(ctx->busy, 0); - set_prio(ctx->busy, PRIO_HIGH); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - /* Pin while using foreign scheduler */ - pin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - set_affinity(ctx->event_2, 1); - send_events( - ctx->event_2, - EVENT_MOVE_BUSY_TO_CPU_1 | EVENT_MOVE_SELF_TO_CPU_0 - | EVENT_SET_SELF_PRIO_TO_LOW | EVENT_SET_BUSY_PRIO_TO_IDLE - ); - set_prio(ctx->event_2, PRIO_VERY_HIGH); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - unpin(blocked); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - set_prio(ctx->busy, PRIO_IDLE); - rtems_mutex_unlock(&ctx->mtx); - rtems_test_assert(rtems_scheduler_get_processor() == 0); - - send_events(ctx->event, EVENT_WAKEUP_MASTER | EVENT_MTX_UNLOCK); - wait_for_events(); - rtems_test_assert(rtems_scheduler_get_processor() == 0); -} - -static void test(test_context *ctx) -{ - rtems_status_code sc; - int run; - - ctx->master = rtems_task_self(); - - rtems_mutex_init(&ctx->mtx, "test"); - - sc = rtems_scheduler_ident(SCHED_A, &ctx->sched_a); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - sc = rtems_scheduler_ident(SCHED_B, &ctx->sched_b); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - sc = rtems_task_create( - rtems_build_name('B', 'U', 'S', 'Y'), - PRIO_HIGH, - RTEMS_MINIMUM_STACK_SIZE, - RTEMS_DEFAULT_MODES, - RTEMS_DEFAULT_ATTRIBUTES, - &ctx->busy - ); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - sc = rtems_task_start(ctx->busy, busy_task, (rtems_task_argument) ctx); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - set_affinity(ctx->busy, 0); - set_prio(ctx->busy, PRIO_IDLE); - rtems_test_assert(rtems_scheduler_get_processor() == 1); - - sc = rtems_task_create( - rtems_build_name('E', 'V', 'T', '1'), - PRIO_LOW, - RTEMS_MINIMUM_STACK_SIZE, - RTEMS_DEFAULT_MODES, - RTEMS_DEFAULT_ATTRIBUTES, - &ctx->event - ); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - sc = rtems_task_start(ctx->event, event_task, (rtems_task_argument) ctx); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - send_events(ctx->event, EVENT_WAKEUP_MASTER); - wait_for_events(); - - sc = rtems_task_create( - rtems_build_name('E', 'V', 'T', '2'), - PRIO_LOW, - RTEMS_MINIMUM_STACK_SIZE, - RTEMS_DEFAULT_MODES, - RTEMS_DEFAULT_ATTRIBUTES, - &ctx->event_2 - ); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - sc = rtems_task_start(ctx->event_2, event_task, (rtems_task_argument) ctx); - rtems_test_assert(sc == RTEMS_SUCCESSFUL); - - send_events(ctx->event_2, EVENT_WAKEUP_MASTER); - wait_for_events(); - - for (run = 1; run <= 3; ++run) { - test_simple_pin_unpin(ctx, run); - test_pin_wait_unpin(ctx, true, run); - test_pin_wait_unpin(ctx, false, run); - test_pin_preempt_unpin(ctx, true, run); - test_pin_preempt_unpin(ctx, false, run); - } - - reconfigure_scheduler(ctx); - - for (run = 1; run <= 3; ++run) { - test_pin_home_no_help_unpin(ctx, true, run); - test_pin_home_no_help_unpin(ctx, false, run); - test_pin_foreign_no_help_unpin(ctx, true, run); - test_pin_foreign_no_help_unpin(ctx, false, run); - } -} - -static void Init(rtems_task_argument arg) -{ - TEST_BEGIN(); - - if (rtems_scheduler_get_processor_maximum() == CPU_COUNT) { - test(&test_instance); - } else { - puts("warning: wrong processor count to run the test"); - } - - TEST_END(); - rtems_test_exit(0); -} - -#define CONFIGURE_APPLICATION_DOES_NOT_NEED_CLOCK_DRIVER -#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER - -#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION - -#define CONFIGURE_MAXIMUM_PROCESSORS CPU_COUNT - -#define CONFIGURE_MAXIMUM_TASKS 4 - -#define CONFIGURE_INIT_TASK_PRIORITY PRIO_MIDDLE - -#define CONFIGURE_RTEMS_INIT_TASKS_TABLE - #define CONFIGURE_SCHEDULER_EDF_SMP #include <rtems/scheduler.h> @@ -603,7 +26,7 @@ RTEMS_SCHEDULER_EDF_SMP(b); #define CONFIGURE_SCHEDULER_TABLE_ENTRIES \ RTEMS_SCHEDULER_TABLE_EDF_SMP(a, SCHED_A), \ - RTEMS_SCHEDULER_TABLE_EDF_SMP(b, SCHED_B) \ + RTEMS_SCHEDULER_TABLE_EDF_SMP(b, SCHED_B) #define CONFIGURE_SCHEDULER_ASSIGNMENTS \ RTEMS_SCHEDULER_ASSIGN(0, RTEMS_SCHEDULER_ASSIGN_PROCESSOR_MANDATORY), \ diff --git a/testsuites/smptests/smpthreadpin02/init.c b/testsuites/smptests/smpthreadpin02/init.c new file mode 100644 index 0000000..fe9e0aa --- /dev/null +++ b/testsuites/smptests/smpthreadpin02/init.c @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @brief Tests pinning with the SMP priority affinity scheduler. + */ + +/* + * Copyright (C) 2021 On-Line Applications Research Corporation (OAR). + * + * 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. + */ + +#include <smpthreadpin.h> + +const char rtems_test_name[] = "SMPTHREADPIN 2"; + +#define CONFIGURE_SCHEDULER_PRIORITY_AFFINITY_SMP + +#include <rtems/scheduler.h> + +RTEMS_SCHEDULER_PRIORITY_AFFINITY_SMP(a, 8); + +RTEMS_SCHEDULER_PRIORITY_AFFINITY_SMP(b, 8); + +#define CONFIGURE_SCHEDULER_TABLE_ENTRIES \ + RTEMS_SCHEDULER_TABLE_PRIORITY_AFFINITY_SMP(a, SCHED_A), \ + RTEMS_SCHEDULER_TABLE_PRIORITY_AFFINITY_SMP(b, SCHED_B) + +#define CONFIGURE_SCHEDULER_ASSIGNMENTS \ + RTEMS_SCHEDULER_ASSIGN(0, RTEMS_SCHEDULER_ASSIGN_PROCESSOR_MANDATORY), \ + RTEMS_SCHEDULER_ASSIGN(0, RTEMS_SCHEDULER_ASSIGN_PROCESSOR_OPTIONAL) + +#define CONFIGURE_INIT + +#include <rtems/confdefs.h> diff --git a/testsuites/smptests/smpthreadpin02/smpthreadpin02.doc b/testsuites/smptests/smpthreadpin02/smpthreadpin02.doc new file mode 100644 index 0000000..e306137 --- /dev/null +++ b/testsuites/smptests/smpthreadpin02/smpthreadpin02.doc @@ -0,0 +1,12 @@ +This file describes the directives and concepts tested by this test set. + +test set name: smpthreadpin02 + +directives: + + - _Thread_Pin() + - _Thread_Unpin() + +concepts: + + - Ensure that the thread to processor pinning works. diff --git a/testsuites/smptests/smpthreadpin02/smpthreadpin02.scn b/testsuites/smptests/smpthreadpin02/smpthreadpin02.scn new file mode 100644 index 0000000..d0b1543 --- /dev/null +++ b/testsuites/smptests/smpthreadpin02/smpthreadpin02.scn @@ -0,0 +1,35 @@ +*** BEGIN OF TEST SMPTHREADPIN 2 *** +*** TEST VERSION: 6.0.0.53a292448f4f064151a3b49f41e43a35aa69dcc2 +*** TEST STATE: EXPECTED_PASS +*** TEST BUILD: RTEMS_POSIX_API RTEMS_SMP +*** TEST TOOLS: 10.3.1 20210409 (RTEMS 6, RSB e845bd5becd4328dc42db3377d44138e23b0d1f7, Newlib eb03ac1) +test simple wait unpin (run 1) +test pin wait unpin (blocked, run 1) +test pin wait unpin (ready, run 1) +test pin preempt unpin (blocked, run 1) +test pin preempt unpin (ready, run 1) +test simple wait unpin (run 2) +test pin wait unpin (blocked, run 2) +test pin wait unpin (ready, run 2) +test pin preempt unpin (blocked, run 2) +test pin preempt unpin (ready, run 2) +test simple wait unpin (run 3) +test pin wait unpin (blocked, run 3) +test pin wait unpin (ready, run 3) +test pin preempt unpin (blocked, run 3) +test pin preempt unpin (ready, run 3) +reconfigure scheduler +test pin home no help unpin (blocked, run 1) +test pin home no help unpin (ready, run 1) +test pin foreign no help unpin (blocked, run 1) +test pin foreign no help unpin (ready, run 1) +test pin home no help unpin (blocked, run 2) +test pin home no help unpin (ready, run 2) +test pin foreign no help unpin (blocked, run 2) +test pin foreign no help unpin (ready, run 2) +test pin home no help unpin (blocked, run 3) +test pin home no help unpin (ready, run 3) +test pin foreign no help unpin (blocked, run 3) +test pin foreign no help unpin (ready, run 3) + +*** END OF TEST SMPTHREADPIN 2 *** -- 1.8.3.1 _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel