1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /****************************************************************************** |
3 | * |
4 | * Copyright © International Business Machines Corp., 2006-2008 |
5 | * |
6 | * DESCRIPTION |
7 | * This test excercises the futex syscall op codes needed for requeuing |
8 | * priority inheritance aware POSIX condition variables and mutexes. |
9 | * |
10 | * AUTHORS |
11 | * Sripathi Kodi <sripathik@in.ibm.com> |
12 | * Darren Hart <dvhart@linux.intel.com> |
13 | * |
14 | * HISTORY |
15 | * 2008-Jan-13: Initial version by Sripathi Kodi <sripathik@in.ibm.com> |
16 | * 2009-Nov-6: futex test adaptation by Darren Hart <dvhart@linux.intel.com> |
17 | * |
18 | *****************************************************************************/ |
19 | |
20 | #define _GNU_SOURCE |
21 | |
22 | #include <errno.h> |
23 | #include <limits.h> |
24 | #include <pthread.h> |
25 | #include <stdio.h> |
26 | #include <stdlib.h> |
27 | #include <signal.h> |
28 | #include <string.h> |
29 | #include "atomic.h" |
30 | #include "futextest.h" |
31 | #include "logging.h" |
32 | |
33 | #define TEST_NAME "futex-requeue-pi" |
34 | #define MAX_WAKE_ITERS 1000 |
35 | #define THREAD_MAX 10 |
36 | #define SIGNAL_PERIOD_US 100 |
37 | |
38 | atomic_t waiters_blocked = ATOMIC_INITIALIZER; |
39 | atomic_t waiters_woken = ATOMIC_INITIALIZER; |
40 | |
41 | futex_t f1 = FUTEX_INITIALIZER; |
42 | futex_t f2 = FUTEX_INITIALIZER; |
43 | futex_t wake_complete = FUTEX_INITIALIZER; |
44 | |
45 | /* Test option defaults */ |
46 | static long timeout_ns; |
47 | static int broadcast; |
48 | static int owner; |
49 | static int locked; |
50 | |
51 | struct thread_arg { |
52 | long id; |
53 | struct timespec *timeout; |
54 | int lock; |
55 | int ret; |
56 | }; |
57 | #define THREAD_ARG_INITIALIZER { 0, NULL, 0, 0 } |
58 | |
59 | void usage(char *prog) |
60 | { |
61 | printf("Usage: %s\n" , prog); |
62 | printf(" -b Broadcast wakeup (all waiters)\n" ); |
63 | printf(" -c Use color\n" ); |
64 | printf(" -h Display this help message\n" ); |
65 | printf(" -l Lock the pi futex across requeue\n" ); |
66 | printf(" -o Use a third party pi futex owner during requeue (cancels -l)\n" ); |
67 | printf(" -t N Timeout in nanoseconds (default: 0)\n" ); |
68 | printf(" -v L Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n" , |
69 | VQUIET, VCRITICAL, VINFO); |
70 | } |
71 | |
72 | int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg, |
73 | int policy, int prio) |
74 | { |
75 | int ret; |
76 | struct sched_param schedp; |
77 | pthread_attr_t attr; |
78 | |
79 | pthread_attr_init(&attr); |
80 | memset(&schedp, 0, sizeof(schedp)); |
81 | |
82 | ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); |
83 | if (ret) { |
84 | error("pthread_attr_setinheritsched\n" , ret); |
85 | return -1; |
86 | } |
87 | |
88 | ret = pthread_attr_setschedpolicy(&attr, policy); |
89 | if (ret) { |
90 | error("pthread_attr_setschedpolicy\n" , ret); |
91 | return -1; |
92 | } |
93 | |
94 | schedp.sched_priority = prio; |
95 | ret = pthread_attr_setschedparam(&attr, &schedp); |
96 | if (ret) { |
97 | error("pthread_attr_setschedparam\n" , ret); |
98 | return -1; |
99 | } |
100 | |
101 | ret = pthread_create(pth, &attr, func, arg); |
102 | if (ret) { |
103 | error("pthread_create\n" , ret); |
104 | return -1; |
105 | } |
106 | return 0; |
107 | } |
108 | |
109 | |
110 | void *waiterfn(void *arg) |
111 | { |
112 | struct thread_arg *args = (struct thread_arg *)arg; |
113 | futex_t old_val; |
114 | |
115 | info("Waiter %ld: running\n" , args->id); |
116 | /* Each thread sleeps for a different amount of time |
117 | * This is to avoid races, because we don't lock the |
118 | * external mutex here */ |
119 | usleep(1000 * (long)args->id); |
120 | |
121 | old_val = f1; |
122 | atomic_inc(&waiters_blocked); |
123 | info("Calling futex_wait_requeue_pi: %p (%u) -> %p\n" , |
124 | &f1, f1, &f2); |
125 | args->ret = futex_wait_requeue_pi(&f1, old_val, &f2, args->timeout, |
126 | FUTEX_PRIVATE_FLAG); |
127 | |
128 | info("waiter %ld woke with %d %s\n" , args->id, args->ret, |
129 | args->ret < 0 ? strerror(errno) : "" ); |
130 | atomic_inc(&waiters_woken); |
131 | if (args->ret < 0) { |
132 | if (args->timeout && errno == ETIMEDOUT) |
133 | args->ret = 0; |
134 | else { |
135 | args->ret = RET_ERROR; |
136 | error("futex_wait_requeue_pi\n" , errno); |
137 | } |
138 | futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); |
139 | } |
140 | futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); |
141 | |
142 | info("Waiter %ld: exiting with %d\n" , args->id, args->ret); |
143 | pthread_exit((void *)&args->ret); |
144 | } |
145 | |
146 | void *broadcast_wakerfn(void *arg) |
147 | { |
148 | struct thread_arg *args = (struct thread_arg *)arg; |
149 | int nr_requeue = INT_MAX; |
150 | int task_count = 0; |
151 | futex_t old_val; |
152 | int nr_wake = 1; |
153 | int i = 0; |
154 | |
155 | info("Waker: waiting for waiters to block\n" ); |
156 | while (waiters_blocked.val < THREAD_MAX) |
157 | usleep(1000); |
158 | usleep(1000); |
159 | |
160 | info("Waker: Calling broadcast\n" ); |
161 | if (args->lock) { |
162 | info("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n" , f2, &f2); |
163 | futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); |
164 | } |
165 | continue_requeue: |
166 | old_val = f1; |
167 | args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, nr_wake, nr_requeue, |
168 | FUTEX_PRIVATE_FLAG); |
169 | if (args->ret < 0) { |
170 | args->ret = RET_ERROR; |
171 | error("FUTEX_CMP_REQUEUE_PI failed\n" , errno); |
172 | } else if (++i < MAX_WAKE_ITERS) { |
173 | task_count += args->ret; |
174 | if (task_count < THREAD_MAX - waiters_woken.val) |
175 | goto continue_requeue; |
176 | } else { |
177 | error("max broadcast iterations (%d) reached with %d/%d tasks woken or requeued\n" , |
178 | 0, MAX_WAKE_ITERS, task_count, THREAD_MAX); |
179 | args->ret = RET_ERROR; |
180 | } |
181 | |
182 | futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG); |
183 | |
184 | if (args->lock) |
185 | futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); |
186 | |
187 | if (args->ret > 0) |
188 | args->ret = task_count; |
189 | |
190 | info("Waker: exiting with %d\n" , args->ret); |
191 | pthread_exit((void *)&args->ret); |
192 | } |
193 | |
194 | void *signal_wakerfn(void *arg) |
195 | { |
196 | struct thread_arg *args = (struct thread_arg *)arg; |
197 | unsigned int old_val; |
198 | int nr_requeue = 0; |
199 | int task_count = 0; |
200 | int nr_wake = 1; |
201 | int i = 0; |
202 | |
203 | info("Waker: waiting for waiters to block\n" ); |
204 | while (waiters_blocked.val < THREAD_MAX) |
205 | usleep(1000); |
206 | usleep(1000); |
207 | |
208 | while (task_count < THREAD_MAX && waiters_woken.val < THREAD_MAX) { |
209 | info("task_count: %d, waiters_woken: %d\n" , |
210 | task_count, waiters_woken.val); |
211 | if (args->lock) { |
212 | info("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n" , |
213 | f2, &f2); |
214 | futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); |
215 | } |
216 | info("Waker: Calling signal\n" ); |
217 | /* cond_signal */ |
218 | old_val = f1; |
219 | args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, |
220 | nr_wake, nr_requeue, |
221 | FUTEX_PRIVATE_FLAG); |
222 | if (args->ret < 0) |
223 | args->ret = -errno; |
224 | info("futex: %x\n" , f2); |
225 | if (args->lock) { |
226 | info("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p\n" , |
227 | f2, &f2); |
228 | futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); |
229 | } |
230 | info("futex: %x\n" , f2); |
231 | if (args->ret < 0) { |
232 | error("FUTEX_CMP_REQUEUE_PI failed\n" , errno); |
233 | args->ret = RET_ERROR; |
234 | break; |
235 | } |
236 | |
237 | task_count += args->ret; |
238 | usleep(SIGNAL_PERIOD_US); |
239 | i++; |
240 | /* we have to loop at least THREAD_MAX times */ |
241 | if (i > MAX_WAKE_ITERS + THREAD_MAX) { |
242 | error("max signaling iterations (%d) reached, giving up on pending waiters.\n" , |
243 | 0, MAX_WAKE_ITERS + THREAD_MAX); |
244 | args->ret = RET_ERROR; |
245 | break; |
246 | } |
247 | } |
248 | |
249 | futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG); |
250 | |
251 | if (args->ret >= 0) |
252 | args->ret = task_count; |
253 | |
254 | info("Waker: exiting with %d\n" , args->ret); |
255 | info("Waker: waiters_woken: %d\n" , waiters_woken.val); |
256 | pthread_exit((void *)&args->ret); |
257 | } |
258 | |
259 | void *third_party_blocker(void *arg) |
260 | { |
261 | struct thread_arg *args = (struct thread_arg *)arg; |
262 | int ret2 = 0; |
263 | |
264 | args->ret = futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); |
265 | if (args->ret) |
266 | goto out; |
267 | args->ret = futex_wait(&wake_complete, wake_complete, NULL, |
268 | FUTEX_PRIVATE_FLAG); |
269 | ret2 = futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); |
270 | |
271 | out: |
272 | if (args->ret || ret2) { |
273 | error("third_party_blocker() futex error" , 0); |
274 | args->ret = RET_ERROR; |
275 | } |
276 | |
277 | pthread_exit((void *)&args->ret); |
278 | } |
279 | |
280 | int unit_test(int broadcast, long lock, int third_party_owner, long timeout_ns) |
281 | { |
282 | void *(*wakerfn)(void *) = signal_wakerfn; |
283 | struct thread_arg blocker_arg = THREAD_ARG_INITIALIZER; |
284 | struct thread_arg waker_arg = THREAD_ARG_INITIALIZER; |
285 | pthread_t waiter[THREAD_MAX], waker, blocker; |
286 | struct timespec ts, *tsp = NULL; |
287 | struct thread_arg args[THREAD_MAX]; |
288 | int *waiter_ret; |
289 | int i, ret = RET_PASS; |
290 | |
291 | if (timeout_ns) { |
292 | time_t secs; |
293 | |
294 | info("timeout_ns = %ld\n" , timeout_ns); |
295 | ret = clock_gettime(CLOCK_MONOTONIC, &ts); |
296 | secs = (ts.tv_nsec + timeout_ns) / 1000000000; |
297 | ts.tv_nsec = ((int64_t)ts.tv_nsec + timeout_ns) % 1000000000; |
298 | ts.tv_sec += secs; |
299 | info("ts.tv_sec = %ld\n" , ts.tv_sec); |
300 | info("ts.tv_nsec = %ld\n" , ts.tv_nsec); |
301 | tsp = &ts; |
302 | } |
303 | |
304 | if (broadcast) |
305 | wakerfn = broadcast_wakerfn; |
306 | |
307 | if (third_party_owner) { |
308 | if (create_rt_thread(&blocker, third_party_blocker, |
309 | (void *)&blocker_arg, SCHED_FIFO, 1)) { |
310 | error("Creating third party blocker thread failed\n" , |
311 | errno); |
312 | ret = RET_ERROR; |
313 | goto out; |
314 | } |
315 | } |
316 | |
317 | atomic_set(&waiters_woken, 0); |
318 | for (i = 0; i < THREAD_MAX; i++) { |
319 | args[i].id = i; |
320 | args[i].timeout = tsp; |
321 | info("Starting thread %d\n" , i); |
322 | if (create_rt_thread(&waiter[i], waiterfn, (void *)&args[i], |
323 | SCHED_FIFO, 1)) { |
324 | error("Creating waiting thread failed\n" , errno); |
325 | ret = RET_ERROR; |
326 | goto out; |
327 | } |
328 | } |
329 | waker_arg.lock = lock; |
330 | if (create_rt_thread(&waker, wakerfn, (void *)&waker_arg, |
331 | SCHED_FIFO, 1)) { |
332 | error("Creating waker thread failed\n" , errno); |
333 | ret = RET_ERROR; |
334 | goto out; |
335 | } |
336 | |
337 | /* Wait for threads to finish */ |
338 | /* Store the first error or failure encountered in waiter_ret */ |
339 | waiter_ret = &args[0].ret; |
340 | for (i = 0; i < THREAD_MAX; i++) |
341 | pthread_join(waiter[i], |
342 | *waiter_ret ? NULL : (void **)&waiter_ret); |
343 | |
344 | if (third_party_owner) |
345 | pthread_join(blocker, NULL); |
346 | pthread_join(waker, NULL); |
347 | |
348 | out: |
349 | if (!ret) { |
350 | if (*waiter_ret) |
351 | ret = *waiter_ret; |
352 | else if (waker_arg.ret < 0) |
353 | ret = waker_arg.ret; |
354 | else if (blocker_arg.ret) |
355 | ret = blocker_arg.ret; |
356 | } |
357 | |
358 | return ret; |
359 | } |
360 | |
361 | int main(int argc, char *argv[]) |
362 | { |
363 | const char *test_name; |
364 | int c, ret; |
365 | |
366 | while ((c = getopt(argc, argv, "bchlot:v:" )) != -1) { |
367 | switch (c) { |
368 | case 'b': |
369 | broadcast = 1; |
370 | break; |
371 | case 'c': |
372 | log_color(1); |
373 | break; |
374 | case 'h': |
375 | usage(prog: basename(argv[0])); |
376 | exit(0); |
377 | case 'l': |
378 | locked = 1; |
379 | break; |
380 | case 'o': |
381 | owner = 1; |
382 | locked = 0; |
383 | break; |
384 | case 't': |
385 | timeout_ns = atoi(optarg); |
386 | break; |
387 | case 'v': |
388 | log_verbosity(atoi(optarg)); |
389 | break; |
390 | default: |
391 | usage(prog: basename(argv[0])); |
392 | exit(1); |
393 | } |
394 | } |
395 | |
396 | ksft_print_header(); |
397 | ksft_set_plan(1); |
398 | ksft_print_msg("%s: Test requeue functionality\n" , basename(argv[0])); |
399 | ksft_print_msg( |
400 | "\tArguments: broadcast=%d locked=%d owner=%d timeout=%ldns\n" , |
401 | broadcast, locked, owner, timeout_ns); |
402 | |
403 | ret = asprintf(&test_name, |
404 | "%s broadcast=%d locked=%d owner=%d timeout=%ldns" , |
405 | TEST_NAME, broadcast, locked, owner, timeout_ns); |
406 | if (ret < 0) { |
407 | ksft_print_msg("Failed to generate test name\n" ); |
408 | test_name = TEST_NAME; |
409 | } |
410 | |
411 | /* |
412 | * FIXME: unit_test is obsolete now that we parse options and the |
413 | * various style of runs are done by run.sh - simplify the code and move |
414 | * unit_test into main() |
415 | */ |
416 | ret = unit_test(broadcast, lock: locked, third_party_owner: owner, timeout_ns); |
417 | |
418 | print_result(test_name, ret); |
419 | return ret; |
420 | } |
421 | |