diff --git a/tests/posix/xsi_realtime_threads/CMakeLists.txt b/tests/posix/xsi_realtime_threads/CMakeLists.txt new file mode 100644 index 0000000000000..8faa9a803471a --- /dev/null +++ b/tests/posix/xsi_realtime_threads/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(xsi_realtime_threads) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) + +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) diff --git a/tests/posix/xsi_realtime_threads/prj.conf b/tests/posix/xsi_realtime_threads/prj.conf new file mode 100644 index 0000000000000..b82ef0577c12d --- /dev/null +++ b/tests/posix/xsi_realtime_threads/prj.conf @@ -0,0 +1,18 @@ +CONFIG_ZTEST=y + +CONFIG_POSIX_API=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_XSI_REALTIME_THREADS=y +CONFIG_ASSERT_VERBOSE=y +CONFIG_POSIX_THREAD_PRIORITY_SCHEDULING=y + +CONFIG_DYNAMIC_THREAD=y +CONFIG_DYNAMIC_THREAD_POOL_SIZE=6 +CONFIG_DYNAMIC_THREAD_STACK_SIZE=2048 +CONFIG_THREAD_STACK_INFO=y +CONFIG_HEAP_MEM_POOL_SIZE=4096 + +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_LOG_DEFAULT_LEVEL=4 diff --git a/tests/posix/xsi_realtime_threads/src/main.c b/tests/posix/xsi_realtime_threads/src/main.c new file mode 100644 index 0000000000000..9ba33d788df4d --- /dev/null +++ b/tests/posix/xsi_realtime_threads/src/main.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2025 Marvin Ouma + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#define BIOS_FOOD 0xB105F00D +#define PRIO_INVALID -1 +#define PTHREAD_INVALID -1 + +static pthread_attr_t attr; +static const pthread_attr_t uninit_attr; +static bool detached_thread_has_finished; + +static bool xsi_realtime_threads_predicate(const void *global_state) +{ + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 1024); + return true; +} + +static void xsi_realtime_threads_teardown(void *fixture) +{ + pthread_attr_destroy(&attr); +} + +static void *thread_entry(void *arg) +{ + bool joinable = (bool)POINTER_TO_UINT(arg); + + if (!joinable) { + detached_thread_has_finished = true; + } + + return NULL; +} + +static void *inheritsched_entry(void *arg) +{ + int prio; + int inheritsched; + int pprio = POINTER_TO_INT(arg); + + zassert_ok(pthread_attr_getinheritsched(&attr, &inheritsched)); + + prio = k_thread_priority_get(k_current_get()); + + if (inheritsched == PTHREAD_INHERIT_SCHED) { + /* + * There will be numerical overlap between posix priorities in different scheduler + * policies so only check the Zephyr priority here. The posix policy and posix + * priority are derived from the Zephyr priority in any case. + */ + zassert_equal(prio, pprio, "actual priority: %d, expected priority: %d", prio, + pprio); + return NULL; + } + + /* inheritsched == PTHREAD_EXPLICIT_SCHED */ + int act_prio; + int exp_prio; + int act_policy; + int exp_policy; + struct sched_param param; + + /* get the actual policy, param, etc */ + zassert_ok(pthread_getschedparam(pthread_self(), &act_policy, ¶m)); + act_prio = param.sched_priority; + + /* get the expected policy, param, etc */ + zassert_ok(pthread_attr_getschedpolicy(&attr, &exp_policy)); + zassert_ok(pthread_attr_getschedparam(&attr, ¶m)); + exp_prio = param.sched_priority; + + /* compare actual vs expected */ + zassert_equal(act_policy, exp_policy, "actual policy: %d, expected policy: %d", act_policy, + exp_policy); + zassert_equal(act_prio, exp_prio, "actual priority: %d, expected priority: %d", act_prio, + exp_prio); + + return NULL; +} + +static void create_thread_common_entry(const pthread_attr_t *attrp, bool expect_success, + bool joinable, void *(*entry)(void *arg), void *arg) +{ + pthread_t th; + + if (!joinable) { + detached_thread_has_finished = false; + } + + if (expect_success) { + zassert_ok(pthread_create(&th, attrp, entry, arg)); + } else { + zassert_not_ok(pthread_create(&th, attrp, entry, arg)); + return; + } + + if (joinable) { + zassert_ok(pthread_join(th, NULL), "failed to join joinable thread"); + return; + } + + /* should not be able to join detached thread */ + zassert_not_ok(pthread_join(th, NULL)); + + for (size_t i = 0; i < 10; ++i) { + k_msleep(2 * CONFIG_PTHREAD_RECYCLER_DELAY_MS); + if (detached_thread_has_finished) { + break; + } + } + + zassert_true(detached_thread_has_finished, "detached thread did not seem to finish"); +} + +static void create_thread_common(const pthread_attr_t *attrp, bool expect_success, bool joinable) +{ + create_thread_common_entry(attrp, expect_success, joinable, thread_entry, + UINT_TO_POINTER(joinable)); +} + +static inline void can_create_thread(const pthread_attr_t *attrp) +{ + create_thread_common(attrp, true, true); +} + +static void test_pthread_attr_setinheritsched_common(bool inheritsched) +{ + int prio; + int policy; + struct sched_param param; + + extern int zephyr_to_posix_priority(int priority, int *policy); + + prio = k_thread_priority_get(k_current_get()); + zassert_not_equal(prio, K_LOWEST_APPLICATION_THREAD_PRIO); + + /* + * values affected by inheritsched are policy / priority / contentionscope + * + * we only support PTHREAD_SCOPE_SYSTEM, so no need to set contentionscope + */ + prio = K_LOWEST_APPLICATION_THREAD_PRIO; + param.sched_priority = zephyr_to_posix_priority(prio, &policy); + + zassert_ok(pthread_attr_setschedpolicy(&attr, policy)); + zassert_ok(pthread_attr_setschedparam(&attr, ¶m)); + zassert_ok(pthread_attr_setinheritsched(&attr, inheritsched)); + create_thread_common_entry(&attr, true, true, inheritsched_entry, + UINT_TO_POINTER(k_thread_priority_get(k_current_get()))); +} + +ZTEST(xsi_realtime_threads, test_pthread_attr_setinheritsched) +{ + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setinheritsched(NULL, PTHREAD_EXPLICIT_SCHED), + EINVAL); + zassert_equal(pthread_attr_setinheritsched(NULL, PTHREAD_INHERIT_SCHED), + EINVAL); + zassert_equal(pthread_attr_setinheritsched((pthread_attr_t *)&uninit_attr, + PTHREAD_INHERIT_SCHED), + EINVAL); + } + zassert_equal(pthread_attr_setinheritsched(&attr, 3), EINVAL); + } + + /* valid cases */ + test_pthread_attr_setinheritsched_common(PTHREAD_INHERIT_SCHED); + test_pthread_attr_setinheritsched_common(PTHREAD_EXPLICIT_SCHED); +} + +ZTEST(xsi_realtime_threads, test_pthread_attr_getinheritsched) +{ + int inheritsched = BIOS_FOOD; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getinheritsched(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getinheritsched(NULL, &inheritsched), EINVAL); + zassert_equal(pthread_attr_getinheritsched(&uninit_attr, &inheritsched), + EINVAL); + } + zassert_equal(pthread_attr_getinheritsched(&attr, NULL), EINVAL); + } + + zassert_ok(pthread_attr_getinheritsched(&attr, &inheritsched)); + zassert_equal(inheritsched, PTHREAD_INHERIT_SCHED); +} + +ZTEST(xsi_realtime_threads, test_pthread_attr_getschedparam) +{ + struct sched_param param = { + .sched_priority = BIOS_FOOD, + }; + + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_getschedparam(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_getschedparam(NULL, ¶m), EINVAL); + zassert_equal(pthread_attr_getschedparam(&uninit_attr, ¶m), EINVAL); + } + zassert_equal(pthread_attr_getschedparam(&attr, NULL), EINVAL); + } + + /* only check to see that the function succeeds and sets param */ + zassert_ok(pthread_attr_getschedparam(&attr, ¶m)); + zassert_not_equal(BIOS_FOOD, param.sched_priority); +} + +ZTEST(xsi_realtime_threads, test_pthread_attr_setschedparam) +{ + struct sched_param param = {0}; + /* degenerate cases */ + { + if (false) { + /* undefined behaviour */ + zassert_equal(pthread_attr_setschedparam(NULL, NULL), EINVAL); + zassert_equal(pthread_attr_setschedparam(NULL, ¶m), EINVAL); + zassert_equal( + pthread_attr_setschedparam((pthread_attr_t *)&uninit_attr, ¶m), + EINVAL); + } + zassert_equal(pthread_attr_setschedparam(&attr, NULL), EINVAL); + } + + zassert_ok(pthread_attr_setschedparam(&attr, ¶m)); + + can_create_thread(&attr); +} + +static void *test_pthread_setschedprio_fn(void *arg) +{ + int policy; + int prio = 0; + struct sched_param param; + pthread_t self = pthread_self(); + + zassert_equal(pthread_setschedprio(self, PRIO_INVALID), EINVAL, "EINVAL was expected"); + zassert_equal(pthread_setschedprio(PTHREAD_INVALID, prio), ESRCH, "ESRCH was expected"); + + zassert_ok(pthread_setschedprio(self, prio)); + param.sched_priority = ~prio; + zassert_ok(pthread_getschedparam(self, &policy, ¶m)); + zassert_equal(param.sched_priority, prio, "Priority unchanged"); + + return NULL; +} + +ZTEST(xsi_realtime_threads, test_pthread_setschedprio) +{ + pthread_t th; + + zassert_ok(pthread_create(&th, NULL, test_pthread_setschedprio_fn, NULL)); + zassert_ok(pthread_join(th, NULL)); +} + +ZTEST_SUITE(xsi_realtime_threads, xsi_realtime_threads_predicate, NULL, NULL, NULL, + xsi_realtime_threads_teardown); diff --git a/tests/posix/xsi_realtime_threads/testcase.yaml b/tests/posix/xsi_realtime_threads/testcase.yaml new file mode 100644 index 0000000000000..4b32ab2ecde78 --- /dev/null +++ b/tests/posix/xsi_realtime_threads/testcase.yaml @@ -0,0 +1,31 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - xsi_realtime_threads + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + min_flash: 64 + min_ram: 32 + timeout: 240 + platform_exclude: + # linker_zephyr_pre0.cmd:140: syntax error (??) + - qemu_xtensa/dc233c + - native_sim + - native_sim/native/64 +tests: + portability.posix.xsi_realtime_threads: {} + portability.posix.xsi_realtime_threads.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + portability.posix.xsi_realtime_threads.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + portability.posix.xsi_realtime_threads.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y