1 /*
2  * Copyright (c) 2024, Meta
3  * Copyright (c) 2024, Marvin Ouma <pancakesdeath@protonmail.com>
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <pthread.h>
9 
10 #include <zephyr/sys/util.h>
11 #include <zephyr/ztest.h>
12 
13 #define BIOS_FOOD 0xB105F00D
14 
15 static bool attr_valid;
16 static pthread_attr_t attr;
17 static const pthread_attr_t uninit_attr;
18 static bool detached_thread_has_finished;
19 
20 /*
21  * This should be discarded by the linker, in this specific testsuite, if
22  * CONFIG_DYNAMIC_THREAD_ALLOC is not set
23  */
24 #define STATIC_THREAD_STACK_SIZE (MAX(1024, PTHREAD_STACK_MIN + CONFIG_TEST_EXTRA_STACK_SIZE))
25 static K_THREAD_STACK_DEFINE(static_thread_stack, STATIC_THREAD_STACK_SIZE);
26 
thread_entry(void * arg)27 static void *thread_entry(void *arg)
28 {
29 	bool joinable = (bool)POINTER_TO_UINT(arg);
30 
31 	if (!joinable) {
32 		detached_thread_has_finished = true;
33 	}
34 
35 	return NULL;
36 }
37 
create_thread_common_entry(const pthread_attr_t * attrp,bool expect_success,bool joinable,void * (* entry)(void * arg),void * arg)38 static void create_thread_common_entry(const pthread_attr_t *attrp, bool expect_success,
39 				       bool joinable, void *(*entry)(void *arg), void *arg)
40 {
41 	pthread_t th;
42 
43 	if (!joinable) {
44 		detached_thread_has_finished = false;
45 	}
46 
47 	if (expect_success) {
48 		zassert_ok(pthread_create(&th, attrp, entry, arg));
49 	} else {
50 		zassert_not_ok(pthread_create(&th, attrp, entry, arg));
51 		return;
52 	}
53 
54 	if (joinable) {
55 		zassert_ok(pthread_join(th, NULL), "failed to join joinable thread");
56 		return;
57 	}
58 
59 	/* should not be able to join detached thread */
60 	zassert_not_ok(pthread_join(th, NULL));
61 
62 	for (size_t i = 0; i < 10; ++i) {
63 		k_msleep(2 * CONFIG_PTHREAD_RECYCLER_DELAY_MS);
64 		if (detached_thread_has_finished) {
65 			break;
66 		}
67 	}
68 
69 	zassert_true(detached_thread_has_finished, "detached thread did not seem to finish");
70 }
71 
create_thread_common(const pthread_attr_t * attrp,bool expect_success,bool joinable)72 static void create_thread_common(const pthread_attr_t *attrp, bool expect_success, bool joinable)
73 {
74 	create_thread_common_entry(attrp, expect_success, joinable, thread_entry,
75 				   UINT_TO_POINTER(joinable));
76 }
77 
can_create_thread(const pthread_attr_t * attrp)78 static inline void can_create_thread(const pthread_attr_t *attrp)
79 {
80 	create_thread_common(attrp, true, true);
81 }
82 
ZTEST(xsi_threads_ext,test_pthread_attr_getstack)83 ZTEST(xsi_threads_ext, test_pthread_attr_getstack)
84 {
85 	void *stackaddr = (void *)BIOS_FOOD;
86 	size_t stacksize = BIOS_FOOD;
87 
88 	/* degenerate cases */
89 	{
90 		if (false) {
91 			/* undefined behaviour */
92 			zassert_equal(pthread_attr_getstack(NULL, NULL, NULL), EINVAL);
93 			zassert_equal(pthread_attr_getstack(NULL, NULL, &stacksize), EINVAL);
94 			zassert_equal(pthread_attr_getstack(NULL, &stackaddr, NULL), EINVAL);
95 			zassert_equal(pthread_attr_getstack(NULL, &stackaddr, &stacksize), EINVAL);
96 			zassert_equal(pthread_attr_getstack(&uninit_attr, &stackaddr, &stacksize),
97 				      EINVAL);
98 		}
99 		zassert_equal(pthread_attr_getstack(&attr, NULL, NULL), EINVAL);
100 		zassert_equal(pthread_attr_getstack(&attr, NULL, &stacksize), EINVAL);
101 		zassert_equal(pthread_attr_getstack(&attr, &stackaddr, NULL), EINVAL);
102 	}
103 
104 	zassert_ok(pthread_attr_getstack(&attr, &stackaddr, &stacksize));
105 	zassert_not_equal(stackaddr, (void *)BIOS_FOOD);
106 	zassert_not_equal(stacksize, BIOS_FOOD);
107 }
108 
ZTEST(xsi_threads_ext,test_pthread_attr_setstack)109 ZTEST(xsi_threads_ext, test_pthread_attr_setstack)
110 {
111 	void *stackaddr;
112 	size_t stacksize;
113 	void *new_stackaddr;
114 	size_t new_stacksize;
115 
116 	/* valid values */
117 	zassert_ok(pthread_attr_getstack(&attr, &stackaddr, &stacksize));
118 
119 	/* degenerate cases */
120 	{
121 		if (false) {
122 			/* undefined behaviour */
123 			zassert_equal(pthread_attr_setstack(NULL, NULL, 0), EACCES);
124 			zassert_equal(pthread_attr_setstack(NULL, NULL, stacksize), EINVAL);
125 			zassert_equal(pthread_attr_setstack(NULL, stackaddr, 0), EINVAL);
126 			zassert_equal(pthread_attr_setstack(NULL, stackaddr, stacksize), EINVAL);
127 			zassert_equal(pthread_attr_setstack((pthread_attr_t *)&uninit_attr,
128 							    stackaddr, stacksize),
129 				      EINVAL);
130 		}
131 		zassert_equal(pthread_attr_setstack(&attr, NULL, 0), EACCES);
132 		zassert_equal(pthread_attr_setstack(&attr, NULL, stacksize), EACCES);
133 		zassert_equal(pthread_attr_setstack(&attr, stackaddr, 0), EINVAL);
134 	}
135 
136 	/* ensure we can create and join a thread with the default attrs */
137 	can_create_thread(&attr);
138 
139 	/* set stack / addr to the current values of stack / addr */
140 	zassert_ok(pthread_attr_setstack(&attr, stackaddr, stacksize));
141 	can_create_thread(&attr);
142 
143 	/* qemu_x86 seems to be unable to set thread stacks to be anything less than 4096 */
144 	if (!IS_ENABLED(CONFIG_X86)) {
145 		/*
146 		 * check we can set a smaller stacksize
147 		 * should not require dynamic reallocation
148 		 * size may get rounded up to some alignment internally
149 		 */
150 		zassert_ok(pthread_attr_setstack(&attr, stackaddr, stacksize - 1));
151 		/* ensure we read back the same values as we specified */
152 		zassert_ok(pthread_attr_getstack(&attr, &new_stackaddr, &new_stacksize));
153 		zassert_equal(new_stackaddr, stackaddr);
154 		zassert_equal(new_stacksize, stacksize - 1);
155 		can_create_thread(&attr);
156 	}
157 
158 	if (IS_ENABLED(CONFIG_DYNAMIC_THREAD_ALLOC)) {
159 		/* ensure we can set a dynamic stack */
160 		k_thread_stack_t *stack;
161 
162 		stack = k_thread_stack_alloc(2 * stacksize, 0);
163 		zassert_not_null(stack);
164 
165 		zassert_ok(pthread_attr_setstack(&attr, (void *)stack, 2 * stacksize));
166 		/* ensure we read back the same values as we specified */
167 		zassert_ok(pthread_attr_getstack(&attr, &new_stackaddr, &new_stacksize));
168 		zassert_equal(new_stackaddr, (void *)stack);
169 		zassert_equal(new_stacksize, 2 * stacksize);
170 		can_create_thread(&attr);
171 	}
172 }
173 
ZTEST(xsi_threads_ext,test_pthread_set_get_concurrency)174 ZTEST(xsi_threads_ext, test_pthread_set_get_concurrency)
175 {
176 	/* EINVAL if the value specified by new_level is negative */
177 	zassert_equal(EINVAL, pthread_setconcurrency(-42));
178 
179 	/*
180 	 * Note: the special value 0 indicates the implementation will
181 	 * maintain the concurrency level at its own discretion.
182 	 *
183 	 * pthread_getconcurrency() should return a value of 0 on init.
184 	 */
185 	zassert_equal(0, pthread_getconcurrency());
186 
187 	for (int i = 0; i <= CONFIG_MP_MAX_NUM_CPUS; ++i) {
188 		zassert_ok(pthread_setconcurrency(i));
189 		/* verify parameter is saved */
190 		zassert_equal(i, pthread_getconcurrency());
191 	}
192 
193 	/* EAGAIN if the a system resource to be exceeded */
194 	zassert_equal(EAGAIN, pthread_setconcurrency(CONFIG_MP_MAX_NUM_CPUS + 1));
195 }
196 
ZTEST(xsi_threads_ext,test_pthread_attr_getstacksize)197 ZTEST(xsi_threads_ext, test_pthread_attr_getstacksize)
198 {
199 	size_t stacksize = BIOS_FOOD;
200 
201 	/* degenerate cases */
202 	{
203 		if (false) {
204 			/* undefined behaviour */
205 			zassert_equal(pthread_attr_getstacksize(NULL, NULL), EINVAL);
206 			zassert_equal(pthread_attr_getstacksize(NULL, &stacksize), EINVAL);
207 			zassert_equal(pthread_attr_getstacksize(&uninit_attr, &stacksize), EINVAL);
208 		}
209 		zassert_equal(pthread_attr_getstacksize(&attr, NULL), EINVAL);
210 	}
211 
212 	zassert_ok(pthread_attr_getstacksize(&attr, &stacksize));
213 	zassert_not_equal(stacksize, BIOS_FOOD);
214 }
215 
ZTEST(xsi_threads_ext,test_pthread_attr_setstacksize)216 ZTEST(xsi_threads_ext, test_pthread_attr_setstacksize)
217 {
218 	size_t stacksize;
219 	size_t new_stacksize;
220 
221 	/* valid size */
222 	zassert_ok(pthread_attr_getstacksize(&attr, &stacksize));
223 
224 	/* degenerate cases */
225 	{
226 		if (false) {
227 			/* undefined behaviour */
228 			zassert_equal(pthread_attr_setstacksize(NULL, 0), EINVAL);
229 			zassert_equal(pthread_attr_setstacksize(NULL, stacksize), EINVAL);
230 			zassert_equal(pthread_attr_setstacksize((pthread_attr_t *)&uninit_attr,
231 								stacksize),
232 				      EINVAL);
233 		}
234 		zassert_equal(pthread_attr_setstacksize(&attr, 0), EINVAL);
235 	}
236 
237 	/* ensure we can spin up a thread with the default stack size */
238 	can_create_thread(&attr);
239 
240 	/* set stack / addr to the current values of stack / addr */
241 	zassert_ok(pthread_attr_setstacksize(&attr, stacksize));
242 	/* ensure we can read back the values we just set */
243 	zassert_ok(pthread_attr_getstacksize(&attr, &new_stacksize));
244 	zassert_equal(new_stacksize, stacksize);
245 	can_create_thread(&attr);
246 
247 	/* qemu_x86 seems to be unable to set thread stacks to be anything less than 4096 */
248 	if (!IS_ENABLED(CONFIG_X86)) {
249 		zassert_ok(pthread_attr_setstacksize(&attr, stacksize - 1));
250 		/* ensure we can read back the values we just set */
251 		zassert_ok(pthread_attr_getstacksize(&attr, &new_stacksize));
252 		zassert_equal(new_stacksize, stacksize - 1);
253 		can_create_thread(&attr);
254 	}
255 
256 	if (IS_ENABLED(CONFIG_DYNAMIC_THREAD_ALLOC)) {
257 		zassert_ok(pthread_attr_setstacksize(&attr, 2 * stacksize));
258 		/* ensure we read back the same values as we specified */
259 		zassert_ok(pthread_attr_getstacksize(&attr, &new_stacksize));
260 		zassert_equal(new_stacksize, 2 * stacksize);
261 		can_create_thread(&attr);
262 	}
263 }
264 
before(void * arg)265 static void before(void *arg)
266 {
267 	ARG_UNUSED(arg);
268 
269 	zassert_ok(pthread_attr_init(&attr));
270 	if (!IS_ENABLED(CONFIG_DYNAMIC_THREAD_ALLOC)) {
271 		zassert_ok(pthread_attr_setstack(&attr, &static_thread_stack,
272 						 STATIC_THREAD_STACK_SIZE));
273 	}
274 	attr_valid = true;
275 }
276 
after(void * arg)277 static void after(void *arg)
278 {
279 	ARG_UNUSED(arg);
280 
281 	if (attr_valid) {
282 		(void)pthread_attr_destroy(&attr);
283 		attr_valid = false;
284 	}
285 }
286 
287 ZTEST_SUITE(xsi_threads_ext, NULL, NULL, before, after, NULL);
288