1#ifdef TEST_MUTEX
2
3#include <crypto/prng.h>
4#include <log.h>
5#include <sch/sched.h>
6#include <sync/mutex.h>
7#include <test.h>
8#include <thread/thread.h>
9
10LOG_SITE_DECLARE(test_mutex, .flags = LOG_SITE_PRINT | LOG_SITE_DROP_OLD,
11 .capacity = LOG_SITE_CAPACITY_DEFAULT,
12 .enabled_mask = LOG_SITE_ALL, .dump_opts = LOG_DUMP_DEFAULT);
13LOG_HANDLE_DECLARE_DEFAULT(test_mutex);
14
15#define test_mutex_log(lvl, fmt, ...) \
16 log(LOG_SITE(test_mutex), LOG_HANDLE(test_mutex), lvl, fmt, ##__VA_ARGS__)
17
18#define test_mutex_err(fmt, ...) test_mutex_log(LOG_ERROR, fmt, ##__VA_ARGS__)
19#define test_mutex_warn(fmt, ...) test_mutex_log(LOG_WARN, fmt, ##__VA_ARGS__)
20#define test_mutex_info(fmt, ...) test_mutex_log(LOG_INFO, fmt, ##__VA_ARGS__)
21#define test_mutex_debug(fmt, ...) test_mutex_log(LOG_DEBUG, fmt, ##__VA_ARGS__)
22#define test_mutex_trace(fmt, ...) test_mutex_log(LOG_TRACE, fmt, ##__VA_ARGS__)
23
24#define MUTEX_REPORT_PROBLEMS() \
25 test_info("Mutex tests are encountering problems and will be skipped"); \
26 return TEST_SKIP(TEST_SKIP_NONE);
27
28static struct mutex basic_test_mtx = MUTEX_INIT;
29
30TEST_DECLARE(mutex_test_basic, .tier = TEST_TIER_UNIT) {
31 mutex_lock(mutex: &basic_test_mtx);
32 scheduler_yield();
33 mutex_unlock(mutex: &basic_test_mtx);
34 return TEST_SUCCESS;
35}
36
37#define MUTEX_MANY_WAITER_TEST_WAITER_COUNT 10
38#define MUTEX_MANY_WAITER_LOOP_COUNT 500
39
40static struct mutex many_mtx = MUTEX_INIT;
41static _Atomic uint32_t many_waiter_done = MUTEX_MANY_WAITER_TEST_WAITER_COUNT;
42
43static void many_worker(void *) {
44 for (int i = 0; i < MUTEX_MANY_WAITER_LOOP_COUNT; i++) {
45 mutex_lock(mutex: &many_mtx);
46 scheduler_yield();
47 mutex_unlock(mutex: &many_mtx);
48 }
49
50 atomic_fetch_sub(&many_waiter_done, 1);
51}
52
53TEST_DECLARE(mutex_many_waiters, .tier = TEST_TIER_INTEGRATION) {
54 for (int i = 0; i < MUTEX_MANY_WAITER_TEST_WAITER_COUNT; i++) {
55 struct thread *t = thread_create(name: "mw", entry_point: many_worker, NULL);
56 t->flags |= THREAD_FLAG_PINNED;
57 thread_enqueue(t);
58 }
59
60 while (atomic_load(&many_waiter_done))
61 scheduler_yield();
62
63 return TEST_SUCCESS;
64}
65
66#define CHAOS_THREAD_COUNT 24
67#define CHAOS_LOOPS 500
68
69static struct mutex chaos_mtx = MUTEX_INIT;
70static _Atomic uint32_t chaos_left = CHAOS_THREAD_COUNT;
71
72static void chaos(void *) {
73 for (int i = 0; i < CHAOS_LOOPS; i++) {
74 mutex_lock(mutex: &chaos_mtx);
75
76 for (volatile size_t j = 0; j < (prng_next() & 0x1F); j++)
77 cpu_relax();
78
79 mutex_unlock(mutex: &chaos_mtx);
80
81 if (prng_next() & 1)
82 scheduler_yield();
83 }
84
85 atomic_fetch_sub(&chaos_left, 1);
86}
87
88volatile struct thread *main_thread = NULL;
89volatile struct thread *other_threads[CHAOS_THREAD_COUNT] = {0};
90
91TEST_DECLARE(mutex_chaos, .tier = TEST_TIER_INTEGRATION) {
92 main_thread = thread_get_current();
93 for (int i = 0; i < CHAOS_THREAD_COUNT; i++)
94 other_threads[i] = thread_spawn(name: "ch", entry: chaos, NULL);
95
96 while (atomic_load(&chaos_left))
97 scheduler_yield();
98
99 return TEST_SUCCESS;
100}
101
102/* we want to spawn a timesharing thread on another core, and
103 * acquire a mutex with it. then we want to spawn a realtime thread
104 * on the same core. the expected behavior is that the timesharing
105 * thread gets boosted to the realtime priority class, allowing it to run
106 * until it drops the lock */
107
108static struct mutex pi_mutex = MUTEX_INIT;
109static struct thread *pi_ts, *pi_rt, *pi_dum;
110static atomic_bool pi_ts_got = false;
111static atomic_uint pi_done = 0;
112
113static void pi_dummy(void *nothing) {
114 (void) nothing;
115 test_mutex_info("dummy");
116 while (atomic_load(&pi_done) < 1)
117 scheduler_yield();
118
119 atomic_fetch_add(&pi_done, 1);
120 test_mutex_info("exiting");
121}
122
123static void pi_rt_thread(void *nothing) {
124 (void) nothing;
125 mutex_lock(mutex: &pi_mutex);
126 test_mutex_info("lock");
127 kassert(mutex_get_owner(&pi_mutex) == thread_get_current());
128 mutex_unlock(mutex: &pi_mutex);
129 test_mutex_info("unlock");
130 atomic_fetch_add(&pi_done, 1);
131 test_mutex_info("exiting");
132}
133
134static void pi_ts_thread(void *nothing) {
135 (void) nothing;
136 mutex_lock(mutex: &pi_mutex);
137 test_mutex_info("lock");
138 atomic_store(&pi_ts_got, true);
139
140 while (thread_get_current()->perceived_prio_class != THREAD_PRIO_CLASS_RT)
141 cpu_relax();
142
143 kassert(mutex_get_owner(&pi_mutex) == thread_get_current());
144 test_mutex_info("boosted");
145
146 test_mutex_info("unlock");
147 mutex_unlock(mutex: &pi_mutex);
148
149 atomic_fetch_add(&pi_done, 1);
150 test_mutex_info("exiting");
151}
152
153TEST_DECLARE(mutex_pi_test, .tier = TEST_TIER_UNIT) {
154 if (global.core_count == 1) {
155 return TEST_SKIP(TEST_SKIP_NONE);
156 }
157
158 cpu_id_t cpu = 1;
159 pi_ts = thread_create(name: "pi_ts", entry_point: pi_ts_thread, NULL);
160 pi_rt = thread_create(name: "pi_rt", entry_point: pi_rt_thread, NULL);
161 pi_dum = thread_create(name: "pi_dum", entry_point: pi_dummy, NULL);
162 pi_rt->perceived_prio_class = THREAD_PRIO_CLASS_RT;
163 pi_dum->perceived_prio_class = THREAD_PRIO_CLASS_RT;
164
165 pi_dum->flags |= THREAD_FLAG_PINNED;
166 pi_ts->flags |= THREAD_FLAG_PINNED;
167 pi_rt->flags |= THREAD_FLAG_PINNED;
168
169 thread_enqueue_on_core(t: pi_ts, core_id: cpu);
170 while (!atomic_load(&pi_ts_got))
171 scheduler_yield();
172
173 thread_enqueue_on_core(t: pi_dum, core_id: cpu);
174 thread_enqueue_on_core(t: pi_rt, core_id: cpu);
175
176 while (atomic_load(&pi_done) < 3)
177 scheduler_yield();
178
179 return TEST_SUCCESS;
180}
181
182static struct mutex pi_mtx_a = MUTEX_INIT;
183static struct mutex pi_mtx_b = MUTEX_INIT;
184
185static struct thread *pi_ts1, *pi_ts2, *pi_rt2;
186static atomic_uint pi_chain_done = 0;
187static atomic_bool ts1_grabbed_a = false;
188static atomic_bool ts2_grabbed_b = false;
189
190static void pi_chain_ts2(void *arg) {
191 (void) arg;
192 mutex_lock(mutex: &pi_mtx_b);
193 test_mutex_info("ts2 lock b");
194 atomic_store(&ts2_grabbed_b, true);
195
196 /* wait until boosted */
197 while (thread_get_current()->perceived_prio_class != THREAD_PRIO_CLASS_RT)
198 cpu_relax();
199
200 test_mutex_info("ts2 boosted");
201 mutex_unlock(mutex: &pi_mtx_b);
202 atomic_fetch_add(&pi_chain_done, 1);
203}
204
205static void pi_chain_ts1(void *arg) {
206 (void) arg;
207 mutex_lock(mutex: &pi_mtx_a);
208 test_mutex_info("ts1 lock a");
209 atomic_store(&ts1_grabbed_a, true);
210
211 /* wait until boosted */
212 while (thread_get_current()->perceived_prio_class != THREAD_PRIO_CLASS_RT)
213 cpu_relax();
214
215 mutex_lock(mutex: &pi_mtx_b);
216 test_mutex_info("ts1 lock b");
217
218 mutex_unlock(mutex: &pi_mtx_b);
219 mutex_unlock(mutex: &pi_mtx_a);
220 atomic_fetch_add(&pi_chain_done, 1);
221}
222
223static void pi_chain_rt(void *arg) {
224 (void) arg;
225 test_mutex_info("rt lock");
226 mutex_lock(mutex: &pi_mtx_a);
227 test_mutex_info("rt lock got");
228
229 mutex_unlock(mutex: &pi_mtx_a);
230 atomic_fetch_add(&pi_chain_done, 1);
231}
232
233TEST_DECLARE(mutex_pi_chain, .tier = TEST_TIER_UNIT) {
234 if (global.core_count < 2) {
235 return TEST_SKIP(TEST_SKIP_NONE);
236 }
237
238 cpu_id_t cpu = 1;
239
240 pi_ts2 = thread_create(name: "pi_ts2", entry_point: pi_chain_ts2, NULL);
241 pi_ts1 = thread_create(name: "pi_ts1", entry_point: pi_chain_ts1, NULL);
242 pi_rt2 = thread_create(name: "pi_rt2", entry_point: pi_chain_rt, NULL);
243
244 pi_rt2->perceived_prio_class = THREAD_PRIO_CLASS_RT;
245
246 pi_ts1->flags |= THREAD_FLAG_PINNED;
247 pi_ts2->flags |= THREAD_FLAG_PINNED;
248 pi_rt2->flags |= THREAD_FLAG_PINNED;
249
250 thread_enqueue_on_core(t: pi_ts2, core_id: cpu);
251 while (!atomic_load(&ts2_grabbed_b))
252 scheduler_yield();
253
254 thread_enqueue_on_core(t: pi_ts1, core_id: cpu);
255
256 /* let ts1 grab A and block on B */
257 while (!atomic_load(&ts1_grabbed_a))
258 scheduler_yield();
259
260 thread_enqueue_on_core(t: pi_rt2, core_id: cpu);
261
262 while (atomic_load(&pi_chain_done) < 3)
263 scheduler_yield();
264
265 return TEST_SUCCESS;
266}
267
268static struct mutex pi_multi_mtx = MUTEX_INIT;
269static atomic_uint pi_multi_done = 0;
270static atomic_bool ts_got = false;
271
272static void pi_multi_ts(void *arg) {
273 (void) arg;
274 mutex_lock(mutex: &pi_multi_mtx);
275 test_mutex_info("multi_ts running");
276 atomic_store(&ts_got, true);
277
278 while (thread_get_current()->perceived_prio_class != THREAD_PRIO_CLASS_RT)
279 cpu_relax();
280
281 test_mutex_info("ts boosted");
282 mutex_unlock(mutex: &pi_multi_mtx);
283 atomic_fetch_add(&pi_multi_done, 1);
284}
285
286static void pi_multi_rt(void *arg) {
287 (void) arg;
288 test_mutex_info("multi_rt running");
289 mutex_lock(mutex: &pi_multi_mtx);
290 mutex_unlock(mutex: &pi_multi_mtx);
291 atomic_fetch_add(&pi_multi_done, 1);
292}
293
294TEST_DECLARE(mutex_pi_multi_waiters, .tier = TEST_TIER_UNIT) {
295 cpu_id_t cpu = 1;
296
297 struct thread *ts = thread_create(name: "pi_ts", entry_point: pi_multi_ts, NULL);
298 struct thread *rt1 = thread_create(name: "pi_rt1", entry_point: pi_multi_rt, NULL);
299 struct thread *rt2 = thread_create(name: "pi_rt2", entry_point: pi_multi_rt, NULL);
300
301 rt1->perceived_prio_class = THREAD_PRIO_CLASS_RT;
302 rt2->perceived_prio_class = THREAD_PRIO_CLASS_RT;
303
304 ts->flags |= THREAD_FLAG_PINNED;
305 rt1->flags |= THREAD_FLAG_PINNED;
306 rt2->flags |= THREAD_FLAG_PINNED;
307
308 thread_enqueue_on_core(t: ts, core_id: cpu);
309 while (!atomic_load(&ts_got))
310 scheduler_yield();
311
312 thread_enqueue_on_core(t: rt1, core_id: cpu);
313 thread_enqueue_on_core(t: rt2, core_id: cpu);
314
315 while (atomic_load(&pi_multi_done) < 3)
316 scheduler_yield();
317
318 return TEST_SUCCESS;
319}
320
321static struct mutex pi_revert_mtx = MUTEX_INIT;
322static atomic_bool pi_reverted = false;
323static atomic_bool pi_revert_got = false;
324static atomic_uint pi_reverted_done = 0;
325
326static void pi_revert_ts(void *arg) {
327 (void) arg;
328 mutex_lock(mutex: &pi_revert_mtx);
329
330 atomic_store(&pi_revert_got, true);
331
332 while (thread_get_current()->perceived_prio_class != THREAD_PRIO_CLASS_RT)
333 cpu_relax();
334
335 mutex_unlock(mutex: &pi_revert_mtx);
336
337 while (thread_get_current()->perceived_prio_class == THREAD_PRIO_CLASS_RT)
338 cpu_relax();
339
340 atomic_store(&pi_reverted, true);
341 atomic_fetch_add(&pi_reverted_done, 1);
342}
343
344static void pi_revert_rt(void *arg) {
345 (void) arg;
346 mutex_lock(mutex: &pi_revert_mtx);
347 mutex_unlock(mutex: &pi_revert_mtx);
348 atomic_fetch_add(&pi_reverted_done, 1);
349}
350
351TEST_DECLARE(mutex_pi_revert, .tier = TEST_TIER_UNIT) {
352 cpu_id_t cpu = 1;
353
354 struct thread *ts = thread_create(name: "pi_ts", entry_point: pi_revert_ts, NULL);
355 struct thread *rt = thread_create(name: "pi_rt", entry_point: pi_revert_rt, NULL);
356
357 rt->perceived_prio_class = THREAD_PRIO_CLASS_RT;
358
359 ts->flags |= THREAD_FLAG_PINNED;
360 rt->flags |= THREAD_FLAG_PINNED;
361
362 thread_enqueue_on_core(t: ts, core_id: cpu);
363
364 while (!atomic_load(&pi_revert_got))
365 scheduler_yield();
366
367 thread_enqueue_on_core(t: rt, core_id: cpu);
368
369 while (!atomic_load(&pi_reverted) || atomic_load(&pi_reverted_done) < 2)
370 scheduler_yield();
371
372 return TEST_SUCCESS;
373}
374
375#endif
376