1 /*
2 * Copyright (c) 2024, Meta
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <pthread.h>
8
9 #include <zephyr/sys/util.h>
10 #include <zephyr/ztest.h>
11
12 #define BIOS_FOOD 0xB105F00D
13 #define SCHED_INVALID 4242
14 #define INVALID_DETACHSTATE 7373
15
16 static bool attr_valid;
17 static pthread_attr_t attr;
18 static const pthread_attr_t uninit_attr;
19 static bool detached_thread_has_finished;
20
21 /* TODO: this should be optional */
22 #define STATIC_THREAD_STACK_SIZE (MAX(1024, PTHREAD_STACK_MIN + CONFIG_TEST_EXTRA_STACK_SIZE))
23 static K_THREAD_STACK_DEFINE(static_thread_stack, STATIC_THREAD_STACK_SIZE);
24
thread_entry(void * arg)25 static void *thread_entry(void *arg)
26 {
27 bool joinable = (bool)POINTER_TO_UINT(arg);
28
29 if (!joinable) {
30 detached_thread_has_finished = true;
31 }
32
33 return NULL;
34 }
35
create_thread_common_entry(const pthread_attr_t * attrp,bool expect_success,bool joinable,void * (* entry)(void * arg),void * arg)36 static void create_thread_common_entry(const pthread_attr_t *attrp, bool expect_success,
37 bool joinable, void *(*entry)(void *arg), void *arg)
38 {
39 pthread_t th;
40
41 if (!joinable) {
42 detached_thread_has_finished = false;
43 }
44
45 if (expect_success) {
46 zassert_ok(pthread_create(&th, attrp, entry, arg));
47 } else {
48 zassert_not_ok(pthread_create(&th, attrp, entry, arg));
49 return;
50 }
51
52 if (joinable) {
53 zassert_ok(pthread_join(th, NULL), "failed to join joinable thread");
54 return;
55 }
56
57 /* should not be able to join detached thread */
58 zassert_not_ok(pthread_join(th, NULL));
59
60 for (size_t i = 0; i < 10; ++i) {
61 k_msleep(2 * CONFIG_PTHREAD_RECYCLER_DELAY_MS);
62 if (detached_thread_has_finished) {
63 break;
64 }
65 }
66
67 zassert_true(detached_thread_has_finished, "detached thread did not seem to finish");
68 }
69
create_thread_common(const pthread_attr_t * attrp,bool expect_success,bool joinable)70 static void create_thread_common(const pthread_attr_t *attrp, bool expect_success, bool joinable)
71 {
72 create_thread_common_entry(attrp, expect_success, joinable, thread_entry,
73 UINT_TO_POINTER(joinable));
74 }
75
can_create_thread(const pthread_attr_t * attrp)76 static inline void can_create_thread(const pthread_attr_t *attrp)
77 {
78 create_thread_common(attrp, true, true);
79 }
80
cannot_create_thread(const pthread_attr_t * attrp)81 static inline void cannot_create_thread(const pthread_attr_t *attrp)
82 {
83 create_thread_common(attrp, false, true);
84 }
85
ZTEST(pthread_attr,test_null_attr)86 ZTEST(pthread_attr, test_null_attr)
87 {
88 /*
89 * This test can only succeed when it is possible to call pthread_create() with a NULL
90 * pthread_attr_t* (I.e. when we have the ability to allocate thread stacks dynamically).
91 */
92 create_thread_common(NULL, IS_ENABLED(CONFIG_DYNAMIC_THREAD) ? true : false, true);
93 }
94
ZTEST(pthread_attr,test_pthread_attr_static_corner_cases)95 ZTEST(pthread_attr, test_pthread_attr_static_corner_cases)
96 {
97 pthread_attr_t attr1;
98
99 Z_TEST_SKIP_IFDEF(CONFIG_DYNAMIC_THREAD);
100
101 /*
102 * These tests are specifically for when dynamic thread stacks are disabled, so passing
103 * a NULL pthread_attr_t* should fail.
104 */
105 cannot_create_thread(NULL);
106
107 /*
108 * Additionally, without calling pthread_attr_setstack(), thread creation should fail.
109 */
110 zassert_ok(pthread_attr_init(&attr1));
111 cannot_create_thread(&attr1);
112 }
113
ZTEST(pthread_attr,test_pthread_attr_init_destroy)114 ZTEST(pthread_attr, test_pthread_attr_init_destroy)
115 {
116 /* attr has already been initialized in before() */
117
118 if (false) {
119 /* undefined behaviour */
120 zassert_ok(pthread_attr_init(&attr));
121 }
122
123 /* cannot destroy an uninitialized attr */
124 zassert_equal(pthread_attr_destroy((pthread_attr_t *)&uninit_attr), EINVAL);
125
126 can_create_thread(&attr);
127
128 /* can destroy an initialized attr */
129 zassert_ok(pthread_attr_destroy(&attr), "failed to destroy an initialized attr");
130 attr_valid = false;
131
132 cannot_create_thread(&attr);
133
134 if (false) {
135 /* undefined behaviour */
136 zassert_ok(pthread_attr_destroy(&attr));
137 }
138
139 /* can re-initialize a destroyed attr */
140 zassert_ok(pthread_attr_init(&attr));
141 /* TODO: pthread_attr_init() should be sufficient to initialize a thread by itself */
142 zassert_ok(pthread_attr_setstack(&attr, &static_thread_stack, STATIC_THREAD_STACK_SIZE));
143 attr_valid = true;
144
145 can_create_thread(&attr);
146
147 /* note: attr is still valid and is destroyed in after() */
148 }
149
ZTEST(pthread_attr,test_pthread_attr_getschedparam)150 ZTEST(pthread_attr, test_pthread_attr_getschedparam)
151 {
152 struct sched_param param = {
153 .sched_priority = BIOS_FOOD,
154 };
155
156 /* degenerate cases */
157 {
158 if (false) {
159 /* undefined behaviour */
160 zassert_equal(pthread_attr_getschedparam(NULL, NULL), EINVAL);
161 zassert_equal(pthread_attr_getschedparam(NULL, ¶m), EINVAL);
162 zassert_equal(pthread_attr_getschedparam(&uninit_attr, ¶m), EINVAL);
163 }
164 zassert_equal(pthread_attr_getschedparam(&attr, NULL), EINVAL);
165 }
166
167 /* only check to see that the function succeeds and sets param */
168 zassert_ok(pthread_attr_getschedparam(&attr, ¶m));
169 zassert_not_equal(BIOS_FOOD, param.sched_priority);
170 }
171
ZTEST(pthread_attr,test_pthread_attr_setschedparam)172 ZTEST(pthread_attr, test_pthread_attr_setschedparam)
173 {
174 struct sched_param param = {0};
175
176 /* degenerate cases */
177 {
178 if (false) {
179 /* undefined behaviour */
180 zassert_equal(pthread_attr_setschedparam(NULL, NULL), EINVAL);
181 zassert_equal(pthread_attr_setschedparam(NULL, ¶m), EINVAL);
182 zassert_equal(
183 pthread_attr_setschedparam((pthread_attr_t *)&uninit_attr, ¶m),
184 EINVAL);
185 }
186 zassert_equal(pthread_attr_setschedparam(&attr, NULL), EINVAL);
187 }
188
189 zassert_ok(pthread_attr_setschedparam(&attr, ¶m));
190
191 can_create_thread(&attr);
192 }
193
ZTEST(pthread_attr,test_pthread_attr_getschedpolicy)194 ZTEST(pthread_attr, test_pthread_attr_getschedpolicy)
195 {
196 int policy = BIOS_FOOD;
197
198 /* degenerate cases */
199 {
200 if (false) {
201 /* undefined behaviour */
202 zassert_equal(pthread_attr_getschedpolicy(NULL, NULL), EINVAL);
203 zassert_equal(pthread_attr_getschedpolicy(NULL, &policy), EINVAL);
204 zassert_equal(pthread_attr_getschedpolicy(&uninit_attr, &policy), EINVAL);
205 }
206 zassert_equal(pthread_attr_getschedpolicy(&attr, NULL), EINVAL);
207 }
208
209 /* only check to see that the function succeeds and sets policy */
210 zassert_ok(pthread_attr_getschedpolicy(&attr, &policy));
211 zassert_not_equal(BIOS_FOOD, policy);
212 }
213
ZTEST(pthread_attr,test_pthread_attr_setschedpolicy)214 ZTEST(pthread_attr, test_pthread_attr_setschedpolicy)
215 {
216 int policy = SCHED_OTHER;
217
218 /* degenerate cases */
219 {
220 if (false) {
221 /* undefined behaviour */
222 zassert_equal(pthread_attr_setschedpolicy(NULL, SCHED_INVALID), EINVAL);
223 zassert_equal(pthread_attr_setschedpolicy(NULL, policy), EINVAL);
224 zassert_equal(
225 pthread_attr_setschedpolicy((pthread_attr_t *)&uninit_attr, policy),
226 EINVAL);
227 }
228 zassert_equal(pthread_attr_setschedpolicy(&attr, SCHED_INVALID), EINVAL);
229 }
230
231 zassert_ok(pthread_attr_setschedpolicy(&attr, SCHED_OTHER));
232 /* read back the same policy we just wrote */
233 policy = SCHED_INVALID;
234 zassert_ok(pthread_attr_getschedpolicy(&attr, &policy));
235 zassert_equal(policy, SCHED_OTHER);
236
237 can_create_thread(&attr);
238 }
239
ZTEST(pthread_attr,test_pthread_attr_getscope)240 ZTEST(pthread_attr, test_pthread_attr_getscope)
241 {
242 int contentionscope = BIOS_FOOD;
243
244 /* degenerate cases */
245 {
246 if (false) {
247 /* undefined behaviour */
248 zassert_equal(pthread_attr_getscope(NULL, NULL), EINVAL);
249 zassert_equal(pthread_attr_getscope(NULL, &contentionscope), EINVAL);
250 zassert_equal(pthread_attr_getscope(&uninit_attr, &contentionscope),
251 EINVAL);
252 }
253 zassert_equal(pthread_attr_getscope(&attr, NULL), EINVAL);
254 }
255
256 zassert_ok(pthread_attr_getscope(&attr, &contentionscope));
257 zassert_equal(contentionscope, PTHREAD_SCOPE_SYSTEM);
258 }
259
ZTEST(pthread_attr,test_pthread_attr_setscope)260 ZTEST(pthread_attr, test_pthread_attr_setscope)
261 {
262 int contentionscope = BIOS_FOOD;
263
264 /* degenerate cases */
265 {
266 if (false) {
267 /* undefined behaviour */
268 zassert_equal(pthread_attr_setscope(NULL, PTHREAD_SCOPE_SYSTEM), EINVAL);
269 zassert_equal(pthread_attr_setscope(NULL, contentionscope), EINVAL);
270 zassert_equal(pthread_attr_setscope((pthread_attr_t *)&uninit_attr,
271 contentionscope),
272 EINVAL);
273 }
274 zassert_equal(pthread_attr_setscope(&attr, 3), EINVAL);
275 }
276
277 zassert_equal(pthread_attr_setscope(&attr, PTHREAD_SCOPE_PROCESS), ENOTSUP);
278 zassert_ok(pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM));
279 zassert_ok(pthread_attr_getscope(&attr, &contentionscope));
280 zassert_equal(contentionscope, PTHREAD_SCOPE_SYSTEM);
281 }
282
ZTEST(pthread_attr,test_pthread_attr_getinheritsched)283 ZTEST(pthread_attr, test_pthread_attr_getinheritsched)
284 {
285 int inheritsched = BIOS_FOOD;
286
287 /* degenerate cases */
288 {
289 if (false) {
290 /* undefined behaviour */
291 zassert_equal(pthread_attr_getinheritsched(NULL, NULL), EINVAL);
292 zassert_equal(pthread_attr_getinheritsched(NULL, &inheritsched), EINVAL);
293 zassert_equal(pthread_attr_getinheritsched(&uninit_attr, &inheritsched),
294 EINVAL);
295 }
296 zassert_equal(pthread_attr_getinheritsched(&attr, NULL), EINVAL);
297 }
298
299 zassert_ok(pthread_attr_getinheritsched(&attr, &inheritsched));
300 zassert_equal(inheritsched, PTHREAD_INHERIT_SCHED);
301 }
302
inheritsched_entry(void * arg)303 static void *inheritsched_entry(void *arg)
304 {
305 int prio;
306 int inheritsched;
307 int pprio = POINTER_TO_INT(arg);
308
309 zassert_ok(pthread_attr_getinheritsched(&attr, &inheritsched));
310
311 prio = k_thread_priority_get(k_current_get());
312
313 if (inheritsched == PTHREAD_INHERIT_SCHED) {
314 /*
315 * There will be numerical overlap between posix priorities in different scheduler
316 * policies so only check the Zephyr priority here. The posix policy and posix
317 * priority are derived from the Zephyr priority in any case.
318 */
319 zassert_equal(prio, pprio, "actual priority: %d, expected priority: %d", prio,
320 pprio);
321 return NULL;
322 }
323
324 /* inheritsched == PTHREAD_EXPLICIT_SCHED */
325 int act_prio;
326 int exp_prio;
327 int act_policy;
328 int exp_policy;
329 struct sched_param param;
330
331 /* get the actual policy, param, etc */
332 zassert_ok(pthread_getschedparam(pthread_self(), &act_policy, ¶m));
333 act_prio = param.sched_priority;
334
335 /* get the expected policy, param, etc */
336 zassert_ok(pthread_attr_getschedpolicy(&attr, &exp_policy));
337 zassert_ok(pthread_attr_getschedparam(&attr, ¶m));
338 exp_prio = param.sched_priority;
339
340 /* compare actual vs expected */
341 zassert_equal(act_policy, exp_policy, "actual policy: %d, expected policy: %d", act_policy,
342 exp_policy);
343 zassert_equal(act_prio, exp_prio, "actual priority: %d, expected priority: %d", act_prio,
344 exp_prio);
345
346 return NULL;
347 }
348
test_pthread_attr_setinheritsched_common(bool inheritsched)349 static void test_pthread_attr_setinheritsched_common(bool inheritsched)
350 {
351 int prio;
352 int policy;
353 struct sched_param param;
354
355 extern int zephyr_to_posix_priority(int priority, int *policy);
356
357 prio = k_thread_priority_get(k_current_get());
358 zassert_not_equal(prio, K_LOWEST_APPLICATION_THREAD_PRIO);
359
360 /*
361 * values affected by inheritsched are policy / priority / contentionscope
362 *
363 * we only support PTHREAD_SCOPE_SYSTEM, so no need to set contentionscope
364 */
365 prio = K_LOWEST_APPLICATION_THREAD_PRIO;
366 param.sched_priority = zephyr_to_posix_priority(prio, &policy);
367
368 zassert_ok(pthread_attr_setschedpolicy(&attr, policy));
369 zassert_ok(pthread_attr_setschedparam(&attr, ¶m));
370 zassert_ok(pthread_attr_setinheritsched(&attr, inheritsched));
371 create_thread_common_entry(&attr, true, true, inheritsched_entry,
372 UINT_TO_POINTER(k_thread_priority_get(k_current_get())));
373 }
374
ZTEST(pthread_attr,test_pthread_attr_setinheritsched)375 ZTEST(pthread_attr, test_pthread_attr_setinheritsched)
376 {
377 /* degenerate cases */
378 {
379 if (false) {
380 /* undefined behaviour */
381 zassert_equal(pthread_attr_setinheritsched(NULL, PTHREAD_EXPLICIT_SCHED),
382 EINVAL);
383 zassert_equal(pthread_attr_setinheritsched(NULL, PTHREAD_INHERIT_SCHED),
384 EINVAL);
385 zassert_equal(pthread_attr_setinheritsched((pthread_attr_t *)&uninit_attr,
386 PTHREAD_INHERIT_SCHED),
387 EINVAL);
388 }
389 zassert_equal(pthread_attr_setinheritsched(&attr, 3), EINVAL);
390 }
391
392 /* valid cases */
393 test_pthread_attr_setinheritsched_common(PTHREAD_INHERIT_SCHED);
394 test_pthread_attr_setinheritsched_common(PTHREAD_EXPLICIT_SCHED);
395 }
396
ZTEST(pthread_attr,test_pthread_attr_large_stacksize)397 ZTEST(pthread_attr, test_pthread_attr_large_stacksize)
398 {
399 size_t actual_size;
400 const size_t expect_size = BIT(CONFIG_POSIX_PTHREAD_ATTR_STACKSIZE_BITS);
401
402 if (pthread_attr_setstacksize(&attr, expect_size) != 0) {
403 TC_PRINT("Unable to allocate large stack of size %zu (skipping)\n", expect_size);
404 ztest_test_skip();
405 return;
406 }
407
408 zassert_ok(pthread_attr_getstacksize(&attr, &actual_size));
409 zassert_equal(actual_size, expect_size);
410 }
411
ZTEST(pthread_attr,test_pthread_attr_getdetachstate)412 ZTEST(pthread_attr, test_pthread_attr_getdetachstate)
413 {
414 int detachstate;
415
416 /* degenerate cases */
417 {
418 if (false) {
419 /* undefined behaviour */
420 zassert_equal(pthread_attr_getdetachstate(NULL, NULL), EINVAL);
421 zassert_equal(pthread_attr_getdetachstate(NULL, &detachstate), EINVAL);
422 zassert_equal(pthread_attr_getdetachstate(&uninit_attr, &detachstate),
423 EINVAL);
424 }
425 zassert_equal(pthread_attr_getdetachstate(&attr, NULL), EINVAL);
426 }
427
428 /* default detachstate is joinable */
429 zassert_ok(pthread_attr_getdetachstate(&attr, &detachstate));
430 zassert_equal(detachstate, PTHREAD_CREATE_JOINABLE);
431 can_create_thread(&attr);
432 }
433
ZTEST(pthread_attr,test_pthread_attr_setdetachstate)434 ZTEST(pthread_attr, test_pthread_attr_setdetachstate)
435 {
436 int detachstate = PTHREAD_CREATE_JOINABLE;
437
438 /* degenerate cases */
439 {
440 if (false) {
441 /* undefined behaviour */
442 zassert_equal(pthread_attr_setdetachstate(NULL, INVALID_DETACHSTATE),
443 EINVAL);
444 zassert_equal(pthread_attr_setdetachstate(NULL, detachstate), EINVAL);
445 zassert_equal(pthread_attr_setdetachstate((pthread_attr_t *)&uninit_attr,
446 detachstate),
447 EINVAL);
448 }
449 zassert_equal(pthread_attr_setdetachstate(&attr, INVALID_DETACHSTATE), EINVAL);
450 }
451
452 /* read back detachstate just written */
453 zassert_ok(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
454 zassert_ok(pthread_attr_getdetachstate(&attr, &detachstate));
455 zassert_equal(detachstate, PTHREAD_CREATE_DETACHED);
456 create_thread_common(&attr, true, false);
457 }
458
ZTEST(pthread_attr,test_pthread_attr_policy_and_priority_limits)459 ZTEST(pthread_attr, test_pthread_attr_policy_and_priority_limits)
460 {
461 int pmin = -1;
462 int pmax = -1;
463 struct sched_param param;
464 static const int policies[] = {
465 SCHED_FIFO,
466 SCHED_RR,
467 SCHED_OTHER,
468 SCHED_INVALID,
469 };
470 static const char *const policy_names[] = {
471 "SCHED_FIFO",
472 "SCHED_RR",
473 "SCHED_OTHER",
474 "SCHED_INVALID",
475 };
476 static const bool policy_enabled[] = {
477 CONFIG_NUM_COOP_PRIORITIES > 0,
478 CONFIG_NUM_PREEMPT_PRIORITIES > 0,
479 CONFIG_NUM_PREEMPT_PRIORITIES > 0,
480 false,
481 };
482 static int nprio[] = {
483 CONFIG_NUM_COOP_PRIORITIES,
484 CONFIG_NUM_PREEMPT_PRIORITIES,
485 CONFIG_NUM_PREEMPT_PRIORITIES,
486 42,
487 };
488 const char *const prios[] = {"pmin", "pmax"};
489
490 BUILD_ASSERT(!(SCHED_INVALID == SCHED_FIFO || SCHED_INVALID == SCHED_RR ||
491 SCHED_INVALID == SCHED_OTHER),
492 "SCHED_INVALID is itself invalid");
493
494 ARRAY_FOR_EACH(policies, policy) {
495 /* get pmin and pmax for policies[policy] */
496 ARRAY_FOR_EACH(prios, i) {
497 errno = 0;
498 if (i == 0) {
499 pmin = sched_get_priority_min(policies[policy]);
500 param.sched_priority = pmin;
501 } else {
502 pmax = sched_get_priority_max(policies[policy]);
503 param.sched_priority = pmax;
504 }
505
506 if (policy == 3) {
507 /* invalid policy */
508 zassert_equal(-1, param.sched_priority);
509 zassert_equal(errno, EINVAL);
510 continue;
511 }
512
513 zassert_not_equal(-1, param.sched_priority,
514 "sched_get_priority_%s(%s) failed: %d",
515 i == 0 ? "min" : "max", policy_names[policy], errno);
516 zassert_ok(errno, "sched_get_priority_%s(%s) set errno to %s",
517 i == 0 ? "min" : "max", policy_names[policy], errno);
518 }
519
520 if (policy != 3) {
521 /* this will not work for SCHED_INVALID */
522
523 /*
524 * IEEE 1003.1-2008 Section 2.8.4
525 * conforming implementations should provide a range of at least 32
526 * priorities
527 *
528 * Note: we relax this requirement
529 */
530 zassert_true(pmax > pmin, "pmax (%d) <= pmin (%d)", pmax, pmin,
531 "%s min/max inconsistency: pmin: %d pmax: %d",
532 policy_names[policy], pmin, pmax);
533
534 /*
535 * Getting into the weeds a bit (i.e. whitebox testing), Zephyr
536 * cooperative threads use [-CONFIG_NUM_COOP_PRIORITIES,-1] and
537 * preemptive threads use [0, CONFIG_NUM_PREEMPT_PRIORITIES - 1],
538 * where the more negative thread has the higher priority. Since we
539 * cannot map those directly (a return value of -1 indicates error),
540 * we simply map those to the positive space.
541 */
542 zassert_equal(pmin, 0, "unexpected pmin for %s", policy_names[policy]);
543 zassert_equal(pmax, nprio[policy] - 1, "unexpected pmax for %s",
544 policy_names[policy]); /* test happy paths */
545 }
546
547 /* create threads with min and max priority levels for each policy */
548 ARRAY_FOR_EACH(prios, i) {
549 param.sched_priority = (i == 0) ? pmin : pmax;
550
551 if (!policy_enabled[policy]) {
552 zassert_not_ok(
553 pthread_attr_setschedpolicy(&attr, policies[policy]));
554 zassert_not_ok(
555 pthread_attr_setschedparam(&attr, ¶m),
556 "pthread_attr_setschedparam() failed for %s (%d) of %s",
557 prios[i], param.sched_priority, policy_names[policy]);
558 continue;
559 }
560
561 /* set policy */
562 zassert_ok(pthread_attr_setschedpolicy(&attr, policies[policy]),
563 "pthread_attr_setschedpolicy() failed for %s (%d) of %s",
564 prios[i], param.sched_priority, policy_names[policy]);
565
566 /* set priority */
567 zassert_ok(pthread_attr_setschedparam(&attr, ¶m),
568 "pthread_attr_setschedparam() failed for %s (%d) of %s",
569 prios[i], param.sched_priority, policy_names[policy]);
570
571 can_create_thread(&attr);
572 }
573 }
574 }
575
before(void * arg)576 static void before(void *arg)
577 {
578 ARG_UNUSED(arg);
579
580 zassert_ok(pthread_attr_init(&attr));
581 /* TODO: pthread_attr_init() should be sufficient to initialize a thread by itself */
582 zassert_ok(pthread_attr_setstack(&attr, &static_thread_stack, STATIC_THREAD_STACK_SIZE));
583 attr_valid = true;
584 }
585
after(void * arg)586 static void after(void *arg)
587 {
588 ARG_UNUSED(arg);
589
590 if (attr_valid) {
591 (void)pthread_attr_destroy(&attr);
592 attr_valid = false;
593 }
594 }
595
596 ZTEST_SUITE(pthread_attr, NULL, NULL, before, after, NULL);
597