1 /*
2  * Copyright (c) 2023, Meta
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "thrd.h"
8 
9 #include <stdint.h>
10 #include <threads.h>
11 
12 #include <zephyr/sys_clock.h>
13 #include <zephyr/ztest.h>
14 
15 static const int valid_mtx_types[] = {
16 	mtx_plain,
17 	mtx_timed,
18 	mtx_plain | mtx_recursive,
19 	mtx_timed | mtx_recursive,
20 };
21 
22 static mtx_t  mutex;
23 static thrd_t th;
24 
ZTEST(libc_mtx,test_mtx_init)25 ZTEST(libc_mtx, test_mtx_init)
26 {
27 	zassert_not_equal(thrd_success, mtx_init(NULL, FORTY_TWO));
28 	zassert_not_equal(thrd_success, mtx_init(&mutex, FORTY_TWO));
29 
30 	if (false) {
31 		/* pthread_mutexattr_init() is not hardened against this */
32 		zassert_not_equal(thrd_success, mtx_init(NULL, mtx_plain));
33 		zassert_not_equal(thrd_success, mtx_init((mtx_t *)BIOS_FOOD, FORTY_TWO));
34 	}
35 
36 	for (size_t i = 0; i < ARRAY_SIZE(valid_mtx_types); ++i) {
37 		int type = valid_mtx_types[i];
38 
39 		zassert_equal(thrd_success, mtx_init(&mutex, type));
40 		mtx_destroy(&mutex);
41 	}
42 }
43 
ZTEST(libc_mtx,test_mtx_destroy)44 ZTEST(libc_mtx, test_mtx_destroy)
45 {
46 	if (false) {
47 		/* degenerate cases */
48 		/* pthread_mutex_destroy() is not hardened against these */
49 		mtx_destroy(NULL);
50 		mtx_destroy((mtx_t *)BIOS_FOOD);
51 	}
52 
53 	zassert_equal(thrd_success, mtx_init(&mutex, mtx_plain));
54 	mtx_destroy(&mutex);
55 }
56 
ZTEST(libc_mtx,test_mtx_lock)57 ZTEST(libc_mtx, test_mtx_lock)
58 {
59 	if (false) {
60 		/* pthread_mutex_lock() is not hardened against this */
61 		zassert_not_equal(thrd_success, mtx_lock(NULL));
62 		zassert_not_equal(thrd_success, mtx_lock((mtx_t *)BIOS_FOOD));
63 	}
64 
65 	/* test plain mutex */
66 	for (size_t i = 0; i < ARRAY_SIZE(valid_mtx_types); ++i) {
67 		int type = valid_mtx_types[i];
68 
69 		zassert_equal(thrd_success, mtx_init(&mutex, type));
70 		zassert_equal(thrd_success, mtx_lock(&mutex));
71 		if ((type & mtx_recursive) == 0) {
72 			if (false) {
73 				/* pthread_mutex_lock() is not hardened against this */
74 				zassert_not_equal(thrd_success, mtx_lock((&mutex)));
75 			}
76 		} else {
77 			zassert_equal(thrd_success, mtx_lock(&mutex));
78 			zassert_equal(thrd_success, mtx_unlock(&mutex));
79 		}
80 		zassert_equal(thrd_success, mtx_unlock(&mutex));
81 		mtx_destroy(&mutex);
82 	}
83 }
84 
85 #define TIMEDLOCK_TIMEOUT_MS       200
86 #define TIMEDLOCK_TIMEOUT_DELAY_MS 100
87 
88 BUILD_ASSERT(TIMEDLOCK_TIMEOUT_DELAY_MS >= 100, "TIMEDLOCK_TIMEOUT_DELAY_MS too small");
89 BUILD_ASSERT(TIMEDLOCK_TIMEOUT_MS >= 2 * TIMEDLOCK_TIMEOUT_DELAY_MS,
90 	     "TIMEDLOCK_TIMEOUT_MS too small");
91 
mtx_timedlock_fn(void * arg)92 static int mtx_timedlock_fn(void *arg)
93 {
94 	struct timespec time_point;
95 	mtx_t *mtx = (mtx_t *)arg;
96 
97 	zassume_ok(clock_gettime(CLOCK_MONOTONIC, &time_point));
98 	timespec_add_ms(&time_point, TIMEDLOCK_TIMEOUT_MS);
99 
100 	return mtx_timedlock(mtx, &time_point);
101 }
102 
ZTEST(libc_mtx,test_mtx_timedlock)103 ZTEST(libc_mtx, test_mtx_timedlock)
104 {
105 	int ret;
106 
107 	/*
108 	 * mtx_timed here is technically unnecessary, because all pthreads can
109 	 * be used for timed locks, but that is sort of peeking into the
110 	 * implementation
111 	 */
112 	zassert_equal(thrd_success, mtx_init(&mutex, mtx_timed));
113 
114 	printk("Expecting timedlock with timeout of %d ms to fail\n", TIMEDLOCK_TIMEOUT_MS);
115 	zassert_equal(thrd_success, mtx_lock(&mutex));
116 	zassert_equal(thrd_success, thrd_create(&th, mtx_timedlock_fn, &mutex));
117 	zassert_equal(thrd_success, thrd_join(th, &ret));
118 	/* ensure timeout occurs */
119 	zassert_equal(thrd_timedout, ret);
120 
121 	printk("Expecting timedlock with timeout of %d ms to succeed after 100ms\n",
122 	       TIMEDLOCK_TIMEOUT_MS);
123 	zassert_equal(thrd_success, thrd_create(&th, mtx_timedlock_fn, &mutex));
124 	/* unlock before timeout expires */
125 	k_msleep(TIMEDLOCK_TIMEOUT_DELAY_MS);
126 	zassert_equal(thrd_success, mtx_unlock(&mutex));
127 	zassert_equal(thrd_success, thrd_join(th, &ret));
128 	/* ensure lock is successful, in spite of delay  */
129 	zassert_equal(thrd_success, ret);
130 
131 	mtx_destroy(&mutex);
132 }
133 
mtx_trylock_fn(void * arg)134 static int mtx_trylock_fn(void *arg)
135 {
136 	mtx_t *mtx = (mtx_t *)arg;
137 
138 	return mtx_trylock(mtx);
139 }
140 
ZTEST(libc_mtx,test_mtx_trylock)141 ZTEST(libc_mtx, test_mtx_trylock)
142 {
143 	int ret;
144 
145 	zassert_equal(thrd_success, mtx_init(&mutex, mtx_plain));
146 
147 	/* ensure trylock fails when lock is held */
148 	zassert_equal(thrd_success, mtx_lock(&mutex));
149 	zassert_equal(thrd_success, thrd_create(&th, mtx_trylock_fn, &mutex));
150 	zassert_equal(thrd_success, thrd_join(th, &ret));
151 	/* ensure lock fails */
152 	zassert_equal(thrd_busy, ret);
153 
154 	mtx_destroy(&mutex);
155 }
156 
ZTEST(libc_mtx,test_mtx_unlock)157 ZTEST(libc_mtx, test_mtx_unlock)
158 {
159 	mtx_t mtx = (mtx_t)BIOS_FOOD;
160 
161 	/* degenerate case */
162 	zassert_not_equal(thrd_success, mtx_unlock(&mtx));
163 
164 	zassert_equal(thrd_success, mtx_init(&mtx, mtx_plain));
165 	zassert_equal(thrd_success, mtx_lock(&mtx));
166 	zassert_equal(thrd_success, mtx_unlock(&mtx));
167 	mtx_destroy(&mtx);
168 }
169 
170 ZTEST_SUITE(libc_mtx, NULL, NULL, NULL, NULL, NULL);
171