/* * Copyright (c) 2024, Meta * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #define BIOS_FOOD 0xB105F00D #define SCHED_INVALID 4242 #define INVALID_DETACHSTATE 7373 static bool attr_valid; static pthread_attr_t attr; static const pthread_attr_t uninit_attr; static bool detached_thread_has_finished; /* TODO: this should be optional */ #define STATIC_THREAD_STACK_SIZE (MAX(1024, PTHREAD_STACK_MIN + CONFIG_TEST_EXTRA_STACK_SIZE)) static K_THREAD_STACK_DEFINE(static_thread_stack, STATIC_THREAD_STACK_SIZE); static void *thread_entry(void *arg) { bool joinable = (bool)POINTER_TO_UINT(arg); if (!joinable) { detached_thread_has_finished = true; } 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 inline void cannot_create_thread(const pthread_attr_t *attrp) { create_thread_common(attrp, false, true); } ZTEST(pthread_attr, test_null_attr) { /* * This test can only succeed when it is possible to call pthread_create() with a NULL * pthread_attr_t* (I.e. when we have the ability to allocate thread stacks dynamically). */ create_thread_common(NULL, IS_ENABLED(CONFIG_DYNAMIC_THREAD) ? true : false, true); } ZTEST(pthread_attr, test_pthread_attr_static_corner_cases) { pthread_attr_t attr1; Z_TEST_SKIP_IFDEF(CONFIG_DYNAMIC_THREAD); /* * These tests are specifically for when dynamic thread stacks are disabled, so passing * a NULL pthread_attr_t* should fail. */ cannot_create_thread(NULL); /* * Additionally, without calling pthread_attr_setstack(), thread creation should fail. */ zassert_ok(pthread_attr_init(&attr1)); cannot_create_thread(&attr1); } ZTEST(pthread_attr, test_pthread_attr_init_destroy) { /* attr has already been initialized in before() */ if (false) { /* undefined behaviour */ zassert_ok(pthread_attr_init(&attr)); } /* cannot destroy an uninitialized attr */ zassert_equal(pthread_attr_destroy((pthread_attr_t *)&uninit_attr), EINVAL); can_create_thread(&attr); /* can destroy an initialized attr */ zassert_ok(pthread_attr_destroy(&attr), "failed to destroy an initialized attr"); attr_valid = false; cannot_create_thread(&attr); if (false) { /* undefined behaviour */ zassert_ok(pthread_attr_destroy(&attr)); } /* can re-initialize a destroyed attr */ zassert_ok(pthread_attr_init(&attr)); /* TODO: pthread_attr_init() should be sufficient to initialize a thread by itself */ zassert_ok(pthread_attr_setstack(&attr, &static_thread_stack, STATIC_THREAD_STACK_SIZE)); attr_valid = true; can_create_thread(&attr); /* note: attr is still valid and is destroyed in after() */ } ZTEST(pthread_attr, 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(pthread_attr, 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); } ZTEST(pthread_attr, test_pthread_attr_getschedpolicy) { int policy = BIOS_FOOD; /* degenerate cases */ { if (false) { /* undefined behaviour */ zassert_equal(pthread_attr_getschedpolicy(NULL, NULL), EINVAL); zassert_equal(pthread_attr_getschedpolicy(NULL, &policy), EINVAL); zassert_equal(pthread_attr_getschedpolicy(&uninit_attr, &policy), EINVAL); } zassert_equal(pthread_attr_getschedpolicy(&attr, NULL), EINVAL); } /* only check to see that the function succeeds and sets policy */ zassert_ok(pthread_attr_getschedpolicy(&attr, &policy)); zassert_not_equal(BIOS_FOOD, policy); } ZTEST(pthread_attr, test_pthread_attr_setschedpolicy) { int policy = SCHED_OTHER; /* degenerate cases */ { if (false) { /* undefined behaviour */ zassert_equal(pthread_attr_setschedpolicy(NULL, SCHED_INVALID), EINVAL); zassert_equal(pthread_attr_setschedpolicy(NULL, policy), EINVAL); zassert_equal( pthread_attr_setschedpolicy((pthread_attr_t *)&uninit_attr, policy), EINVAL); } zassert_equal(pthread_attr_setschedpolicy(&attr, SCHED_INVALID), EINVAL); } zassert_ok(pthread_attr_setschedpolicy(&attr, SCHED_OTHER)); /* read back the same policy we just wrote */ policy = SCHED_INVALID; zassert_ok(pthread_attr_getschedpolicy(&attr, &policy)); zassert_equal(policy, SCHED_OTHER); can_create_thread(&attr); } ZTEST(pthread_attr, test_pthread_attr_getscope) { int contentionscope = BIOS_FOOD; /* degenerate cases */ { if (false) { /* undefined behaviour */ zassert_equal(pthread_attr_getscope(NULL, NULL), EINVAL); zassert_equal(pthread_attr_getscope(NULL, &contentionscope), EINVAL); zassert_equal(pthread_attr_getscope(&uninit_attr, &contentionscope), EINVAL); } zassert_equal(pthread_attr_getscope(&attr, NULL), EINVAL); } zassert_ok(pthread_attr_getscope(&attr, &contentionscope)); zassert_equal(contentionscope, PTHREAD_SCOPE_SYSTEM); } ZTEST(pthread_attr, test_pthread_attr_setscope) { int contentionscope = BIOS_FOOD; /* degenerate cases */ { if (false) { /* undefined behaviour */ zassert_equal(pthread_attr_setscope(NULL, PTHREAD_SCOPE_SYSTEM), EINVAL); zassert_equal(pthread_attr_setscope(NULL, contentionscope), EINVAL); zassert_equal(pthread_attr_setscope((pthread_attr_t *)&uninit_attr, contentionscope), EINVAL); } zassert_equal(pthread_attr_setscope(&attr, 3), EINVAL); } zassert_equal(pthread_attr_setscope(&attr, PTHREAD_SCOPE_PROCESS), ENOTSUP); zassert_ok(pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM)); zassert_ok(pthread_attr_getscope(&attr, &contentionscope)); zassert_equal(contentionscope, PTHREAD_SCOPE_SYSTEM); } ZTEST(pthread_attr, 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); } 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 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(pthread_attr, 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(pthread_attr, test_pthread_attr_large_stacksize) { size_t actual_size; const size_t expect_size = BIT(CONFIG_POSIX_PTHREAD_ATTR_STACKSIZE_BITS); if (pthread_attr_setstacksize(&attr, expect_size) != 0) { TC_PRINT("Unable to allocate large stack of size %zu (skipping)\n", expect_size); ztest_test_skip(); return; } zassert_ok(pthread_attr_getstacksize(&attr, &actual_size)); zassert_equal(actual_size, expect_size); } ZTEST(pthread_attr, test_pthread_attr_getdetachstate) { int detachstate; /* degenerate cases */ { if (false) { /* undefined behaviour */ zassert_equal(pthread_attr_getdetachstate(NULL, NULL), EINVAL); zassert_equal(pthread_attr_getdetachstate(NULL, &detachstate), EINVAL); zassert_equal(pthread_attr_getdetachstate(&uninit_attr, &detachstate), EINVAL); } zassert_equal(pthread_attr_getdetachstate(&attr, NULL), EINVAL); } /* default detachstate is joinable */ zassert_ok(pthread_attr_getdetachstate(&attr, &detachstate)); zassert_equal(detachstate, PTHREAD_CREATE_JOINABLE); can_create_thread(&attr); } ZTEST(pthread_attr, test_pthread_attr_setdetachstate) { int detachstate = PTHREAD_CREATE_JOINABLE; /* degenerate cases */ { if (false) { /* undefined behaviour */ zassert_equal(pthread_attr_setdetachstate(NULL, INVALID_DETACHSTATE), EINVAL); zassert_equal(pthread_attr_setdetachstate(NULL, detachstate), EINVAL); zassert_equal(pthread_attr_setdetachstate((pthread_attr_t *)&uninit_attr, detachstate), EINVAL); } zassert_equal(pthread_attr_setdetachstate(&attr, INVALID_DETACHSTATE), EINVAL); } /* read back detachstate just written */ zassert_ok(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); zassert_ok(pthread_attr_getdetachstate(&attr, &detachstate)); zassert_equal(detachstate, PTHREAD_CREATE_DETACHED); create_thread_common(&attr, true, false); } ZTEST(pthread_attr, test_pthread_attr_policy_and_priority_limits) { int pmin = -1; int pmax = -1; struct sched_param param; static const int policies[] = { SCHED_FIFO, SCHED_RR, SCHED_OTHER, SCHED_INVALID, }; static const char *const policy_names[] = { "SCHED_FIFO", "SCHED_RR", "SCHED_OTHER", "SCHED_INVALID", }; static const bool policy_enabled[] = { CONFIG_NUM_COOP_PRIORITIES > 0, CONFIG_NUM_PREEMPT_PRIORITIES > 0, CONFIG_NUM_PREEMPT_PRIORITIES > 0, false, }; static int nprio[] = { CONFIG_NUM_COOP_PRIORITIES, CONFIG_NUM_PREEMPT_PRIORITIES, CONFIG_NUM_PREEMPT_PRIORITIES, 42, }; const char *const prios[] = {"pmin", "pmax"}; BUILD_ASSERT(!(SCHED_INVALID == SCHED_FIFO || SCHED_INVALID == SCHED_RR || SCHED_INVALID == SCHED_OTHER), "SCHED_INVALID is itself invalid"); ARRAY_FOR_EACH(policies, policy) { /* get pmin and pmax for policies[policy] */ ARRAY_FOR_EACH(prios, i) { errno = 0; if (i == 0) { pmin = sched_get_priority_min(policies[policy]); param.sched_priority = pmin; } else { pmax = sched_get_priority_max(policies[policy]); param.sched_priority = pmax; } if (policy == 3) { /* invalid policy */ zassert_equal(-1, param.sched_priority); zassert_equal(errno, EINVAL); continue; } zassert_not_equal(-1, param.sched_priority, "sched_get_priority_%s(%s) failed: %d", i == 0 ? "min" : "max", policy_names[policy], errno); zassert_ok(errno, "sched_get_priority_%s(%s) set errno to %s", i == 0 ? "min" : "max", policy_names[policy], errno); } if (policy != 3) { /* this will not work for SCHED_INVALID */ /* * IEEE 1003.1-2008 Section 2.8.4 * conforming implementations should provide a range of at least 32 * priorities * * Note: we relax this requirement */ zassert_true(pmax > pmin, "pmax (%d) <= pmin (%d)", pmax, pmin, "%s min/max inconsistency: pmin: %d pmax: %d", policy_names[policy], pmin, pmax); /* * Getting into the weeds a bit (i.e. whitebox testing), Zephyr * cooperative threads use [-CONFIG_NUM_COOP_PRIORITIES,-1] and * preemptive threads use [0, CONFIG_NUM_PREEMPT_PRIORITIES - 1], * where the more negative thread has the higher priority. Since we * cannot map those directly (a return value of -1 indicates error), * we simply map those to the positive space. */ zassert_equal(pmin, 0, "unexpected pmin for %s", policy_names[policy]); zassert_equal(pmax, nprio[policy] - 1, "unexpected pmax for %s", policy_names[policy]); /* test happy paths */ } /* create threads with min and max priority levels for each policy */ ARRAY_FOR_EACH(prios, i) { param.sched_priority = (i == 0) ? pmin : pmax; if (!policy_enabled[policy]) { zassert_not_ok( pthread_attr_setschedpolicy(&attr, policies[policy])); zassert_not_ok( pthread_attr_setschedparam(&attr, ¶m), "pthread_attr_setschedparam() failed for %s (%d) of %s", prios[i], param.sched_priority, policy_names[policy]); continue; } /* set policy */ zassert_ok(pthread_attr_setschedpolicy(&attr, policies[policy]), "pthread_attr_setschedpolicy() failed for %s (%d) of %s", prios[i], param.sched_priority, policy_names[policy]); /* set priority */ zassert_ok(pthread_attr_setschedparam(&attr, ¶m), "pthread_attr_setschedparam() failed for %s (%d) of %s", prios[i], param.sched_priority, policy_names[policy]); can_create_thread(&attr); } } } static void before(void *arg) { ARG_UNUSED(arg); zassert_ok(pthread_attr_init(&attr)); /* TODO: pthread_attr_init() should be sufficient to initialize a thread by itself */ zassert_ok(pthread_attr_setstack(&attr, &static_thread_stack, STATIC_THREAD_STACK_SIZE)); attr_valid = true; } static void after(void *arg) { ARG_UNUSED(arg); if (attr_valid) { (void)pthread_attr_destroy(&attr); attr_valid = false; } } ZTEST_SUITE(pthread_attr, NULL, NULL, before, after, NULL);