421 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/******************************************************************************3 *4 * Copyright © International Business Machines Corp., 2006-20085 *6 * DESCRIPTION7 * This test excercises the futex syscall op codes needed for requeuing8 * priority inheritance aware POSIX condition variables and mutexes.9 *10 * AUTHORS11 * Sripathi Kodi <sripathik@in.ibm.com>12 * Darren Hart <dvhart@linux.intel.com>13 *14 * HISTORY15 * 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_SOURCE21 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 100035#define THREAD_MAX 1036#define SIGNAL_PERIOD_US 10037 38atomic_t waiters_blocked = ATOMIC_INITIALIZER;39atomic_t waiters_woken = ATOMIC_INITIALIZER;40 41futex_t f1 = FUTEX_INITIALIZER;42futex_t f2 = FUTEX_INITIALIZER;43futex_t wake_complete = FUTEX_INITIALIZER;44 45/* Test option defaults */46static long timeout_ns;47static int broadcast;48static int owner;49static int locked;50 51struct 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 59void 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 72int 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 110void *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 time117 * This is to avoid races, because we don't lock the118 * 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 146void *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 194void *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 259void *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 280int 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 348out: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 361int main(int argc, char *argv[])362{363 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(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(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 the413 * various style of runs are done by run.sh - simplify the code and move414 * unit_test into main()415 */416 ret = unit_test(broadcast, locked, owner, timeout_ns);417 418 print_result(test_name, ret);419 return ret;420}421