diff options
Diffstat (limited to 'tools/testing/selftests/futex')
25 files changed, 4268 insertions, 0 deletions
diff --git a/tools/testing/selftests/futex/Makefile b/tools/testing/selftests/futex/Makefile new file mode 100644 index 000000000..78ab2cd11 --- /dev/null +++ b/tools/testing/selftests/futex/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0 +SUBDIRS := functional + +TEST_PROGS := run.sh + +include ../lib.mk + +all: + @for DIR in $(SUBDIRS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + mkdir $$BUILD_TARGET -p; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@;\ + if [ -e $$DIR/$(TEST_PROGS) ]; then \ + rsync -a $$DIR/$(TEST_PROGS) $$BUILD_TARGET/; \ + fi \ + done + +override define INSTALL_RULE + mkdir -p $(INSTALL_PATH) + install -t $(INSTALL_PATH) $(TEST_PROGS) $(TEST_PROGS_EXTENDED) $(TEST_FILES) + + @for SUBDIR in $(SUBDIRS); do \ + BUILD_TARGET=$(OUTPUT)/$$SUBDIR; \ + mkdir $$BUILD_TARGET -p; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$SUBDIR INSTALL_PATH=$(INSTALL_PATH)/$$SUBDIR install; \ + done; +endef + +override define CLEAN + @for DIR in $(SUBDIRS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + mkdir $$BUILD_TARGET -p; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@;\ + done +endef diff --git a/tools/testing/selftests/futex/README b/tools/testing/selftests/futex/README new file mode 100644 index 000000000..f3926c33e --- /dev/null +++ b/tools/testing/selftests/futex/README @@ -0,0 +1,62 @@ +Futex Test +========== +Futex Test is intended to thoroughly test the Linux kernel futex system call +API. + +Functional tests shall test the documented behavior of the futex operation +code under test. This includes checking for proper behavior under normal use, +odd corner cases, regression tests, and abject abuse and misuse. + +Futextest will also provide example implementation of mutual exclusion +primitives. These can be used as is in user applications or can serve as +examples for system libraries. These will likely be added to either a new lib/ +directory or purely as header files under include/, I'm leaning toward the +latter. + +Quick Start +----------- +# make +# ./run.sh + +Design and Implementation Goals +------------------------------- +o Tests should be as self contained as is practical so as to facilitate sharing + the individual tests on mailing list discussions and bug reports. +o The build system shall remain as simple as possible, avoiding any archive or + shared object building and linking. +o Where possible, any helper functions or other package-wide code shall be + implemented in header files, avoiding the need to compile intermediate object + files. +o External dependencies shall remain as minimal as possible. Currently gcc + and glibc are the only dependencies. +o Tests return 0 for success and < 0 for failure. + +Output Formatting +----------------- +Test output shall be easily parsable by both human and machine. Title and +results are printed to stdout, while intermediate ERROR or FAIL messages are +sent to stderr. Tests shall support the -c option to print PASS, FAIL, and +ERROR strings in color for easy visual parsing. Output shall conform to the +following format: + +test_name: Description of the test + Arguments: arg1=val1 #units specified for clarity where appropriate + ERROR: Description of unexpected error + FAIL: Reason for test failure + # FIXME: Perhaps an " INFO: informational message" option would be + # useful here. Using -v to toggle it them on and off, as with -c. + # there may be multiple ERROR or FAIL messages +Result: (PASS|FAIL|ERROR) + +Naming +------ +o FIXME: decide on a sane test naming scheme. Currently the tests are named + based on the primary futex operation they test. Eventually this will become a + problem as we intend to write multiple tests which collide in this namespace. + Perhaps something like "wait-wake-1" "wait-wake-2" is adequate, leaving the + detailed description in the test source and the output. + +Coding Style +------------ +o The Futex Test project adheres to the coding standards set forth by Linux + kernel as defined in the Linux source Documentation/process/coding-style.rst. diff --git a/tools/testing/selftests/futex/functional/.gitignore b/tools/testing/selftests/futex/functional/.gitignore new file mode 100644 index 000000000..7c39d10b3 --- /dev/null +++ b/tools/testing/selftests/futex/functional/.gitignore @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +futex_lock_pi_exiting +futex_numa_mpol +futex_priv_hash +futex_requeue +futex_requeue_pi +futex_requeue_pi_mismatched_ops +futex_requeue_pi_signal_restart +futex_wait +futex_wait_private_mapped_file +futex_wait_timeout +futex_wait_uninitialized_heap +futex_wait_wouldblock +futex_waitv +futex_numa +robust_list diff --git a/tools/testing/selftests/futex/functional/Makefile b/tools/testing/selftests/futex/functional/Makefile new file mode 100644 index 000000000..a03bd5acb --- /dev/null +++ b/tools/testing/selftests/futex/functional/Makefile @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 +PKG_CONFIG ?= pkg-config +LIBNUMA_TEST = $(shell sh -c "$(PKG_CONFIG) numa --atleast-version 2.0.18 > /dev/null 2>&1 && echo SUFFICIENT || echo NO") + +INCLUDES := -I../include -I../../ $(KHDR_INCLUDES) +CFLAGS := $(CFLAGS) -g -O2 -Wall -pthread -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64 $(INCLUDES) $(KHDR_INCLUDES) -DLIBNUMA_VER_$(LIBNUMA_TEST)=1 +LDLIBS := -lpthread -lrt +ifeq ($(LIBNUMA_TEST),SUFFICIENT) +LDLIBS += -lnuma +endif + +LOCAL_HDRS := \ + ../include/futextest.h \ + ../include/atomic.h \ + ../include/futex_thread.h +TEST_GEN_PROGS := \ + futex_wait_timeout \ + futex_wait_wouldblock \ + futex_requeue_pi \ + futex_requeue_pi_signal_restart \ + futex_requeue_pi_mismatched_ops \ + futex_wait_uninitialized_heap \ + futex_wait_private_mapped_file \ + futex_wait \ + futex_requeue \ + futex_priv_hash \ + futex_numa_mpol \ + futex_waitv \ + futex_numa \ + robust_list \ + futex_lock_pi_exiting + +TEST_PROGS := run.sh + +top_srcdir = ../../../../.. +DEFAULT_INSTALL_HDR_PATH := 1 +include ../../lib.mk diff --git a/tools/testing/selftests/futex/functional/futex_lock_pi_exiting.c b/tools/testing/selftests/futex/functional/futex_lock_pi_exiting.c new file mode 100644 index 000000000..623c5f3a1 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_lock_pi_exiting.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * futex_lock_pi_exiting.c + * + * Coverage for the FUTEX_LOCK_PI owner-exiting path. futex_wait_timeout.c + * already covers FUTEX_LOCK_PI timeout semantics and robust_list.c covers + * owner death via the robust list, but nothing exercises FUTEX_LOCK_PI when a + * non-robust PI owner exits while holding the lock, nor the basic ownership / + * EDEADLK / unlock word semantics. + * + * DESCRIPTION + * Three tests: + * + * 1. lock_unlock_basic - uncontended FUTEX_LOCK_PI semantics: the futex + * word carries the owner TID, a recursive lock by the owner returns + * EDEADLK, and FUTEX_UNLOCK_PI clears the word. + * + * 2. owner_dies_with_blocked_waiter - a thread acquires a PI futex and + * exits while holding it. do_exit() runs futex_cleanup_begin() (which + * flips the task's futex state to FUTEX_STATE_EXITING) and + * exit_pi_state_list() (which hands off / tears down the pi_state). A + * contending FUTEX_LOCK_PI waiter must end up in one of: + * + * 0 - ownership was transferred to / acquired by the waiter + * EOWNERDEAD - previous owner died holding the lock; the caller is + * now the owner and must acknowledge by unlocking + * ESRCH - the owner encoded in the futex word is already gone + * + * and on the first two it must actually own the lock afterwards. + * + * 3. stress_owner_exits - hammer that same exiting-owner path. This is + * where the following bug lived: the 'exiting' task pointer was not + * reset at the retry label, so after wait_for_owner_exiting() dropped + * its reference a subsequent retry that returned a non-EBUSY error fed + * the stale pointer back in and tripped WARN_ON_ONCE(exiting). That + * warning is invisible to user space, so this test cannot observe it + * through a syscall return value; it only becomes a visible failure + * (crash) on a kernel booted with panic_on_warn=1 (or built with + * CONFIG_BUG_ON_DATA_CORRUPTION). The loop drives the path so that + * such a kernel trips on it - the canonical way fuzz/CI catch these. + * + * Fix: 210d36d892de ("futex: Clear stale exiting pointer in + * futex_lock_pi() retry path") + * Fixes: 3ef240eaff36 ("futex: Prevent exit livelock") + * + * AUTHOR + * Based on futex test boilerplate by Darren Hart <dvhart@linux.intel.com> + * + *****************************************************************************/ + +#define _GNU_SOURCE + +#include <errno.h> +#include <pthread.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <sys/syscall.h> + +#include "futextest.h" +#include "kselftest_harness.h" + +/* + * Iterations for the stress variant. Enough to repeatedly land in the narrow + * EXITING window while keeping the test fast. + */ +#define STRESS_ITERS 1000 + +static futex_t pi_futex; +static pthread_barrier_t locked_barrier; +static pthread_barrier_t release_barrier; + +static pid_t sys_gettid(void) +{ + return syscall(SYS_gettid); +} + +/* + * Owner thread: acquire the PI futex and exit while still holding it. Two + * modes: + * park == 0: signal that we hold the lock, then exit immediately (racy; the + * waiter races against our exit path). + * park == 1: signal that we hold the lock and keep holding until released + * via release_barrier, so a waiter has time to contend as a real + * PI waiter before we die. + */ +static void *owner_thread(void *arg) +{ + long park = (long)arg; + + if (futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG) != 0) + return (void *)(intptr_t)-errno; + + pthread_barrier_wait(&locked_barrier); + + if (park) + pthread_barrier_wait(&release_barrier); + + /* Die while still holding the lock. */ + pthread_exit((void *)0); +} + +/* + * Block on the PI futex as a waiter. Returns 0 on acquisition, otherwise the + * positive errno. + */ +static int waiter_lock_pi(void) +{ + int ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG); + + return ret == 0 ? 0 : errno; +} + +static int outcome_ok(int outcome) +{ + return outcome == 0 || outcome == EOWNERDEAD || outcome == ESRCH; +} + +/* Results published by waiter_thread() for the owning thread to assert on. */ +static int waiter_outcome; +static int waiter_owns; + +/* + * Waiter thread for the blocked-waiter test. Contends for the lock and, when + * it acquires, records whether the futex word actually carries its TID and + * releases the lock itself (FUTEX_UNLOCK_PI must run in the owning thread). + */ +static void *waiter_thread(void *arg) +{ + pid_t tid = sys_gettid(); + + waiter_outcome = waiter_lock_pi(); + if (waiter_outcome == 0 || waiter_outcome == EOWNERDEAD) { + waiter_owns = (pi_futex & FUTEX_TID_MASK) == (futex_t)tid; + futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG); + } + return NULL; +} + +FIXTURE(lock_pi_exiting) { +}; + +FIXTURE_SETUP(lock_pi_exiting) { +} + +FIXTURE_TEARDOWN(lock_pi_exiting) { +} + +/* + * Uncontended FUTEX_LOCK_PI semantics, fully deterministic. + */ +TEST_F(lock_pi_exiting, lock_unlock_basic) +{ + pid_t tid = sys_gettid(); + int ret; + + pi_futex = FUTEX_INITIALIZER; + + /* Acquire: we become the owner, our TID lands in the futex word. */ + ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG); + ASSERT_EQ(ret, 0) + TH_LOG("lock failed: errno=%d (%s)", errno, strerror(errno)); + ASSERT_EQ(pi_futex & FUTEX_TID_MASK, (futex_t)tid) + TH_LOG("owner TID not in futex word: 0x%08x", pi_futex); + + /* A recursive lock by the owner must be refused, not deadlock. */ + errno = 0; + ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EDEADLK) + TH_LOG("recursive lock: expected EDEADLK, got errno=%d", errno); + + /* Release: the futex word is handed back clean. */ + ret = futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG); + ASSERT_EQ(ret, 0) + TH_LOG("unlock failed: errno=%d", errno); + ASSERT_EQ(pi_futex, (futex_t)0) + TH_LOG("futex word not cleared after unlock: 0x%08x", pi_futex); +} + +/* + * A PI waiter inherits the lock when the owner dies holding it. + * + * The owner parks while holding the lock, this thread contends for it, then + * the owner exits. The waiter must come out cleanly (no hang, no unexpected + * error) and, when it acquires, must actually own the lock. + */ +TEST_F(lock_pi_exiting, owner_dies_with_blocked_waiter) +{ + pthread_t owner, waiter; + + pthread_barrier_init(&locked_barrier, NULL, 2); + pthread_barrier_init(&release_barrier, NULL, 2); + pi_futex = FUTEX_INITIALIZER; + waiter_outcome = -1; + waiter_owns = 0; + + ASSERT_EQ(pthread_create(&owner, NULL, owner_thread, (void *)1), 0); + + /* Wait until the owner actually holds the lock. */ + pthread_barrier_wait(&locked_barrier); + + /* Start the waiter and give it time to block as a real PI waiter. */ + ASSERT_EQ(pthread_create(&waiter, NULL, waiter_thread, NULL), 0); + usleep(1000); + + /* Release the owner so it dies while the waiter is queued on it. */ + pthread_barrier_wait(&release_barrier); + + pthread_join(waiter, NULL); + pthread_join(owner, NULL); + + ASSERT_TRUE(outcome_ok(waiter_outcome)) { + TH_LOG("unexpected FUTEX_LOCK_PI outcome: %d (%s)", + waiter_outcome, strerror(waiter_outcome)); + } + if (waiter_outcome == 0 || waiter_outcome == EOWNERDEAD) { + ASSERT_TRUE(waiter_owns) + TH_LOG("waiter acquired but futex word lacks its TID"); + } + + pthread_barrier_destroy(&locked_barrier); + pthread_barrier_destroy(&release_barrier); +} + +/* + * Stress: repeatedly let an owner exit while a waiter contends for the lock. + * + * Each iteration drives the FUTEX_STATE_EXITING -> -EBUSY -> retry path that + * the stale-'exiting'-pointer bug lived on (210d36d892de). The warning it + * fixed is invisible to user space, so on a normally-configured kernel both + * the buggy and fixed kernels pass here; the point is to make a kernel booted + * with panic_on_warn=1 trip during one of these iterations. + */ +TEST_F(lock_pi_exiting, stress_owner_exits) +{ + for (int i = 0; i < STRESS_ITERS; i++) { + pthread_t owner; + int outcome; + + pthread_barrier_init(&locked_barrier, NULL, 2); + pi_futex = FUTEX_INITIALIZER; + + ASSERT_EQ(pthread_create(&owner, NULL, owner_thread, (void *)0), 0); + + /* Owner holds the lock; race FUTEX_LOCK_PI against its exit. */ + pthread_barrier_wait(&locked_barrier); + + outcome = waiter_lock_pi(); + ASSERT_TRUE(outcome_ok(outcome)) { + TH_LOG("iter %d: unexpected outcome %d (%s)", + i, outcome, strerror(outcome)); + } + if (outcome == 0 || outcome == EOWNERDEAD) + futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG); + + pthread_join(owner, NULL); + pthread_barrier_destroy(&locked_barrier); + } +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_numa.c b/tools/testing/selftests/futex/functional/futex_numa.c new file mode 100644 index 000000000..e0a33510c --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_numa.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <pthread.h> +#include <sys/shm.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <assert.h> +#include "futextest.h" +#include "futex2test.h" + +typedef u_int32_t u32; +typedef int32_t s32; +typedef u_int64_t u64; + +static unsigned int fflags = (FUTEX2_SIZE_U32 | FUTEX2_PRIVATE); +static int fnode = FUTEX_NO_NODE; + +/* fairly stupid test-and-set lock with a waiter flag */ + +#define N_LOCK 0x0000001 +#define N_WAITERS 0x0001000 + +struct futex_numa_32 { + union { + u64 full; + struct { + u32 val; + u32 node; + }; + }; +}; + +void futex_numa_32_lock(struct futex_numa_32 *lock) +{ + for (;;) { + struct futex_numa_32 new, old = { + .full = __atomic_load_n(&lock->full, __ATOMIC_RELAXED), + }; + + for (;;) { + new = old; + if (old.val == 0) { + /* no waiter, no lock -> first lock, set no-node */ + new.node = fnode; + } + if (old.val & N_LOCK) { + /* contention, set waiter */ + new.val |= N_WAITERS; + } + new.val |= N_LOCK; + + /* nothing changed, ready to block */ + if (old.full == new.full) + break; + + /* + * Use u64 cmpxchg to set the futex value and node in a + * consistent manner. + */ + if (__atomic_compare_exchange_n(&lock->full, + &old.full, new.full, + /* .weak */ false, + __ATOMIC_ACQUIRE, + __ATOMIC_RELAXED)) { + + /* if we just set N_LOCK, we own it */ + if (!(old.val & N_LOCK)) + return; + + /* go block */ + break; + } + } + + futex2_wait(lock, new.val, fflags, NULL, 0); + } +} + +void futex_numa_32_unlock(struct futex_numa_32 *lock) +{ + u32 val = __atomic_sub_fetch(&lock->val, N_LOCK, __ATOMIC_RELEASE); + assert((s32)val >= 0); + if (val & N_WAITERS) { + int woken = futex2_wake(lock, 1, fflags); + assert(val == N_WAITERS); + if (!woken) { + __atomic_compare_exchange_n(&lock->val, &val, 0U, + false, __ATOMIC_RELAXED, + __ATOMIC_RELAXED); + } + } +} + +static long nanos = 50000; + +struct thread_args { + pthread_t tid; + volatile int * done; + struct futex_numa_32 *lock; + int val; + int *val1, *val2; + int node; +}; + +static void *threadfn(void *_arg) +{ + struct thread_args *args = _arg; + struct timespec ts = { + .tv_nsec = nanos, + }; + int node; + + while (!*args->done) { + + futex_numa_32_lock(args->lock); + args->val++; + + assert(*args->val1 == *args->val2); + (*args->val1)++; + nanosleep(&ts, NULL); + (*args->val2)++; + + node = args->lock->node; + futex_numa_32_unlock(args->lock); + + if (node != args->node) { + args->node = node; + printf("node: %d\n", node); + } + + nanosleep(&ts, NULL); + } + + return NULL; +} + +static void *contendfn(void *_arg) +{ + struct thread_args *args = _arg; + + while (!*args->done) { + /* + * futex2_wait() will take hb-lock, verify *var == val and + * queue/abort. By knowingly setting val 'wrong' this will + * abort and thereby generate hb-lock contention. + */ + futex2_wait(&args->lock->val, ~0U, fflags, NULL, 0); + args->val++; + } + + return NULL; +} + +static volatile int done = 0; +static struct futex_numa_32 lock = { .val = 0, }; +static int val1, val2; + +int main(int argc, char *argv[]) +{ + struct thread_args *tas[512], *cas[512]; + int c, t, threads = 2, contenders = 0; + int sleeps = 10; + int total = 0; + + while ((c = getopt(argc, argv, "c:t:s:n:N::")) != -1) { + switch (c) { + case 'c': + contenders = atoi(optarg); + break; + case 't': + threads = atoi(optarg); + break; + case 's': + sleeps = atoi(optarg); + break; + case 'n': + nanos = atoi(optarg); + break; + case 'N': + fflags |= FUTEX2_NUMA; + if (optarg) + fnode = atoi(optarg); + break; + default: + exit(1); + break; + } + } + + for (t = 0; t < contenders; t++) { + struct thread_args *args = calloc(1, sizeof(*args)); + if (!args) { + perror("thread_args"); + exit(-1); + } + + args->done = &done; + args->lock = &lock; + args->val1 = &val1; + args->val2 = &val2; + args->node = -1; + + if (pthread_create(&args->tid, NULL, contendfn, args)) { + perror("pthread_create"); + exit(-1); + } + + cas[t] = args; + } + + for (t = 0; t < threads; t++) { + struct thread_args *args = calloc(1, sizeof(*args)); + if (!args) { + perror("thread_args"); + exit(-1); + } + + args->done = &done; + args->lock = &lock; + args->val1 = &val1; + args->val2 = &val2; + args->node = -1; + + if (pthread_create(&args->tid, NULL, threadfn, args)) { + perror("pthread_create"); + exit(-1); + } + + tas[t] = args; + } + + sleep(sleeps); + + done = true; + + for (t = 0; t < threads; t++) { + struct thread_args *args = tas[t]; + + pthread_join(args->tid, NULL); + total += args->val; +// printf("tval: %d\n", args->val); + } + printf("total: %d\n", total); + + if (contenders) { + total = 0; + for (t = 0; t < contenders; t++) { + struct thread_args *args = cas[t]; + + pthread_join(args->tid, NULL); + total += args->val; + // printf("tval: %d\n", args->val); + } + printf("contenders: %d\n", total); + } + + return 0; +} + diff --git a/tools/testing/selftests/futex/functional/futex_numa_mpol.c b/tools/testing/selftests/futex/functional/futex_numa_mpol.c new file mode 100644 index 000000000..4ffcf41ef --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_numa_mpol.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de> + */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#ifdef LIBNUMA_VER_SUFFICIENT +#include <numa.h> +#include <numaif.h> +#endif + +#include <linux/futex.h> +#include <sys/mman.h> + +#include "futextest.h" +#include "futex2test.h" +#include "kselftest_harness.h" + +#define MAX_THREADS 64 + +static pthread_barrier_t barrier_main; +static pthread_t threads[MAX_THREADS]; + +struct thread_args { + void *futex_ptr; + unsigned int flags; + int result; +}; + +static struct thread_args thread_args[MAX_THREADS]; + +#ifndef FUTEX_NO_NODE +#define FUTEX_NO_NODE (-1) +#endif + +#ifndef FUTEX2_MPOL +#define FUTEX2_MPOL 0x08 +#endif + +static void *thread_lock_fn(void *arg) +{ + struct thread_args *args = arg; + int ret; + + pthread_barrier_wait(&barrier_main); + ret = futex2_wait(args->futex_ptr, 0, args->flags, NULL, 0); + args->result = ret; + return NULL; +} + +static void create_max_threads(struct __test_metadata *_metadata, void *futex_ptr) +{ + int i, ret; + + for (i = 0; i < MAX_THREADS; i++) { + thread_args[i].futex_ptr = futex_ptr; + thread_args[i].flags = FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA; + thread_args[i].result = 0; + ret = pthread_create(&threads[i], NULL, thread_lock_fn, &thread_args[i]); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); + } +} + +static void join_max_threads(struct __test_metadata *_metadata) +{ + int i, ret; + + for (i = 0; i < MAX_THREADS; i++) { + ret = pthread_join(threads[i], NULL); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_join failed for thread %d", i); + } +} + +static void __test_futex(struct __test_metadata *_metadata, void *futex_ptr, int err_value, + unsigned int futex_flags) +{ + int to_wake, ret, i; + + pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); + create_max_threads(_metadata, futex_ptr); + pthread_barrier_wait(&barrier_main); + to_wake = MAX_THREADS; + + do { + ret = futex2_wake(futex_ptr, to_wake, futex_flags); + + if (err_value) { + EXPECT_LT(ret, 0) { + TH_LOG("futex2_wake(%d, 0x%x) should fail, but didn't", + to_wake, futex_flags); + } + + EXPECT_EQ(errno, err_value) { + TH_LOG("futex2_wake(%d, 0x%x) expected error was %d, but returned %d (%s)", + to_wake, futex_flags, err_value, errno, strerror(errno)); + } + + break; + } + if (ret < 0) { + if (errno == ENOSYS || (errno == EINVAL && (futex_flags & FUTEX2_NUMA))) + SKIP(return, "futex2 or FUTEX2_NUMA not supported by kernel"); + + ASSERT_GE(ret, 0) { + TH_LOG("Failed futex2_wake(%d, 0x%x): %s", + to_wake, futex_flags, strerror(errno)); + } + } + if (!ret) + usleep(50); + to_wake -= ret; + + } while (to_wake); + join_max_threads(_metadata); + + for (i = 0; i < MAX_THREADS; i++) { + if (err_value) { + EXPECT_EQ(thread_args[i].result, -1) { + TH_LOG("Thread %d should fail but succeeded (%d)", + i, thread_args[i].result); + } + } else { + EXPECT_EQ(thread_args[i].result, 0) + TH_LOG("Thread %d failed (%d)", i, thread_args[i].result); + } + } +} + +static void test_futex(struct __test_metadata *_metadata, void *futex_ptr, int err_value) +{ + __test_futex(_metadata, futex_ptr, err_value, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA); +} + +TEST(futex_numa_mpol) +{ + struct futex32_numa *futex_numa; + void *futex_ptr; + int mem_size; + + mem_size = sysconf(_SC_PAGE_SIZE); + futex_ptr = mmap(NULL, mem_size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + ASSERT_NE(futex_ptr, MAP_FAILED) + TH_LOG("mmap() for %d bytes failed: %s", mem_size, strerror(errno)); + + /* Create an invalid memory region for the "Memory out of range" test */ + mprotect(futex_ptr + mem_size, mem_size, PROT_NONE); + + futex_numa = futex_ptr; + + TH_LOG("Regular test"); + futex_numa->futex = 0; + futex_numa->numa = FUTEX_NO_NODE; + test_futex(_metadata, futex_ptr, 0); + + EXPECT_NE(futex_numa->numa, FUTEX_NO_NODE) + TH_LOG("NUMA node is left uninitialized"); + + /* FUTEX2_NUMA futex must be 8-byte aligned */ + TH_LOG("Mis-aligned futex"); + test_futex(_metadata, futex_ptr + mem_size - 4, EINVAL); + + TH_LOG("Memory out of range"); + test_futex(_metadata, futex_ptr + mem_size, EFAULT); + + futex_numa->numa = FUTEX_NO_NODE; + mprotect(futex_ptr, mem_size, PROT_READ); + TH_LOG("Memory, RO"); + test_futex(_metadata, futex_ptr, EFAULT); + + mprotect(futex_ptr, mem_size, PROT_NONE); + TH_LOG("Memory, no access"); + test_futex(_metadata, futex_ptr, EFAULT); + + mprotect(futex_ptr, mem_size, PROT_READ | PROT_WRITE); + TH_LOG("Memory back to RW"); + test_futex(_metadata, futex_ptr, 0); + + /* MPOL test. Does not work as expected */ +#ifdef LIBNUMA_VER_SUFFICIENT + for (int i = 0; i < 4; i++) { + unsigned long nodemask; + int ret; + + nodemask = 1 << i; + ret = mbind(futex_ptr, mem_size, MPOL_BIND, &nodemask, + sizeof(nodemask) * 8, 0); + if (ret == 0) { + ret = numa_set_mempolicy_home_node(futex_ptr, mem_size, i, 0); + ASSERT_EQ(ret, 0) + TH_LOG("Failed to set home node: %s, %d", strerror(errno), errno); + + TH_LOG("Node %d test", i); + futex_numa->futex = 0; + futex_numa->numa = FUTEX_NO_NODE; + + ret = futex2_wake(futex_ptr, 0, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | + FUTEX2_NUMA | FUTEX2_MPOL); + EXPECT_GE(ret, 0) + TH_LOG("Failed to wake 0 with MPOL: %s", strerror(errno)); + EXPECT_EQ(futex_numa->numa, i) + TH_LOG("Returned NUMA node is %d expected %d", futex_numa->numa, i); + } + } +#else + SKIP(return, "futex2 MPOL hints test requires libnuma 2.0.18+"); +#endif + munmap(futex_ptr, mem_size * 2); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_priv_hash.c b/tools/testing/selftests/futex/functional/futex_priv_hash.c new file mode 100644 index 000000000..a8742e204 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_priv_hash.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de> + */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <linux/prctl.h> +#include <sys/prctl.h> + +#include "kselftest_harness.h" + +#define MAX_THREADS 64 + +static pthread_barrier_t barrier_main; +static pthread_mutex_t global_lock; +static pthread_t threads[MAX_THREADS]; +static int counter; + +#ifndef PR_FUTEX_HASH +#define PR_FUTEX_HASH 78 +# define PR_FUTEX_HASH_SET_SLOTS 1 +# define PR_FUTEX_HASH_GET_SLOTS 2 +#endif + +static int futex_hash_slots_set(unsigned int slots) +{ + return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0); +} + +static int futex_hash_slots_get(void) +{ + return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS); +} + +static void futex_hash_slots_set_verify(struct __test_metadata *_metadata, int slots) +{ + int ret; + + ret = futex_hash_slots_set(slots); + ASSERT_EQ(ret, 0) + TH_LOG("Failed to set slots to %d: %s", slots, strerror(errno)); + + ret = futex_hash_slots_get(); + ASSERT_EQ(ret, slots) { + TH_LOG("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %s", + slots, ret, strerror(errno)); + } +} + +static void futex_hash_slots_set_must_fail(struct __test_metadata *_metadata, int slots) +{ + int ret; + + ret = futex_hash_slots_set(slots); + EXPECT_LT(ret, 0) + TH_LOG("futex_hash_slots_set(%d) should fail but succeeded", slots); +} + +static void *thread_return_fn(void *arg) +{ + return NULL; +} + +static void *thread_lock_fn(void *arg) +{ + pthread_barrier_wait(&barrier_main); + + pthread_mutex_lock(&global_lock); + counter++; + usleep(20); + pthread_mutex_unlock(&global_lock); + return NULL; +} + +static void create_max_threads(struct __test_metadata *_metadata, void *(*thread_fn)(void *)) +{ + int i, ret; + + for (i = 0; i < MAX_THREADS; i++) { + ret = pthread_create(&threads[i], NULL, thread_fn, NULL); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed: %s", strerror(errno)); + } +} + +static void join_max_threads(struct __test_metadata *_metadata) +{ + int i, ret; + + for (i = 0; i < MAX_THREADS; i++) { + ret = pthread_join(threads[i], NULL); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_join failed for thread %d: %s", i, strerror(errno)); + } +} + +#define SEC_IN_NSEC 1000000000 +#define MSEC_IN_NSEC 1000000 + +static void futex_dummy_op(struct __test_metadata *_metadata) +{ + pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + struct timespec timeout; + int ret; + + pthread_mutex_lock(&lock); + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_nsec += 100 * MSEC_IN_NSEC; + if (timeout.tv_nsec >= SEC_IN_NSEC) { + timeout.tv_nsec -= SEC_IN_NSEC; + timeout.tv_sec++; + } + ret = pthread_mutex_timedlock(&lock, &timeout); + ASSERT_NE(ret, 0) + TH_LOG("Successfully locked an already locked mutex"); + + ASSERT_EQ(ret, ETIMEDOUT) + TH_LOG("pthread_mutex_timedlock() did not timeout: %d", ret); +} + +static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n"; +static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n"; + +TEST(priv_hash) +{ + int futex_slots1, futex_slotsn, online_cpus; + pthread_mutexattr_t mutex_attr_pi; + int ret, retry = 20; + + ret = pthread_mutexattr_init(&mutex_attr_pi); + ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT); + ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi); + ASSERT_EQ(ret, 0) + TH_LOG("Failed to initialize pthread mutex"); + + /* First thread, expect to be 0, not yet initialized */ + ret = futex_hash_slots_get(); + if (ret < 0 && errno == EINVAL) + SKIP(return, "PR_FUTEX_HASH not supported by kernel"); + + ASSERT_EQ(ret, 0) + TH_LOG("futex_hash_slots_get() failed: %d, %s", ret, strerror(errno)); + + ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create() failed: %d, %s", ret, strerror(errno)); + + ret = pthread_join(threads[0], NULL); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_join() failed: %d, %s", ret, strerror(errno)); + + /* First thread, has to initialize private hash */ + futex_slots1 = futex_hash_slots_get(); + EXPECT_GT(futex_slots1, 0) + TH_LOG("Current hash buckets: %d. %s", futex_slots1, test_msg_auto_create); + + online_cpus = sysconf(_SC_NPROCESSORS_ONLN); + ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); + + ret = pthread_mutex_lock(&global_lock); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_mutex_lock failed: %s", strerror(errno)); + + counter = 0; + create_max_threads(_metadata, thread_lock_fn); + pthread_barrier_wait(&barrier_main); + + /* + * The current default size of hash buckets is 16. The auto increase + * works only if more than 16 CPUs are available. + */ + TH_LOG("Online CPUs: %d", online_cpus); + if (online_cpus > 16) { +retry_getslots: + futex_slotsn = futex_hash_slots_get(); + if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) { + retry--; + /* + * Auto scaling on thread creation can be slightly delayed + * because it waits for a RCU grace period twice. The new + * private hash is assigned upon the first futex operation + * after grace period. + * To cover all this for testing purposes the function + * below will acquire a lock and acquire it again with a + * 100ms timeout which must timeout. This ensures we + * sleep for 100ms and issue a futex operation. + */ + if (retry > 0) { + futex_dummy_op(_metadata); + goto retry_getslots; + } + EXPECT_NE(futex_slots1, futex_slotsn) { + TH_LOG("Expected increase of hash buckets but got: %d -> %d. %s", + futex_slots1, futex_slotsn, test_msg_auto_inc); + } + } + } else { + SKIP(return, "Automatic increase with more than 16 CPUs (only %d online)", online_cpus); + } + ret = pthread_mutex_unlock(&global_lock); + + /* Once the user changes it, it has to be what is set */ + futex_hash_slots_set_verify(_metadata, 2); + futex_hash_slots_set_verify(_metadata, 4); + futex_hash_slots_set_verify(_metadata, 8); + futex_hash_slots_set_verify(_metadata, 32); + futex_hash_slots_set_verify(_metadata, 16); + + ret = futex_hash_slots_set(15); + EXPECT_LT(ret, 0) + TH_LOG("Use 15 slots should fail but succeeded"); + + futex_hash_slots_set_verify(_metadata, 2); + join_max_threads(_metadata); + + EXPECT_EQ(counter, MAX_THREADS) + TH_LOG("Created and waited for %d of %d threads", counter, MAX_THREADS); + + counter = 0; + /* Once the user set something, auto resize must be disabled */ + ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); + + create_max_threads(_metadata, thread_lock_fn); + join_max_threads(_metadata); + + ret = futex_hash_slots_get(); + EXPECT_EQ(ret, 2) + TH_LOG("No more auto-resize after manual setting, got %d", ret); + + futex_hash_slots_set_must_fail(_metadata, 1 << 29); + futex_hash_slots_set_verify(_metadata, 4); + + /* + * Once the global hash has been requested, then this requested can not + * be undone. + */ + ret = futex_hash_slots_set(0); + ASSERT_EQ(ret, 0) + TH_LOG("Global hash request failed: %s", strerror(errno)); + + futex_hash_slots_set_must_fail(_metadata, 4); + futex_hash_slots_set_must_fail(_metadata, 8); + futex_hash_slots_set_must_fail(_metadata, 8); + futex_hash_slots_set_must_fail(_metadata, 0); + futex_hash_slots_set_must_fail(_metadata, 6); + + ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); + + create_max_threads(_metadata, thread_lock_fn); + join_max_threads(_metadata); + + ret = futex_hash_slots_get(); + EXPECT_EQ(ret, 0) + TH_LOG("Continue to use global hash failed"); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue.c b/tools/testing/selftests/futex/functional/futex_requeue.c new file mode 100644 index 000000000..cc31f0517 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_requeue.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright Collabora Ltd., 2021 + * + * futex cmp requeue test by André Almeida <andrealmeid@collabora.com> + */ + +#include <limits.h> +#include <pthread.h> +#include <string.h> + +#include "futextest.h" +#include "futex_thread.h" +#include "kselftest_harness.h" + +struct waiter_args { + struct __test_metadata *_metadata; + unsigned int n_threads; +}; + +volatile futex_t *f1; + +static int waiterfn(void *arg) +{ + struct __test_metadata *_metadata; + struct waiter_args *wargs = arg; + struct timespec to = { }; + int res; + + _metadata = wargs->_metadata; + to.tv_sec = (wargs->n_threads + 1) * WAIT_FOR_THREAD_SECS; + + res = futex_wait(f1, *f1, &to, 0); + if (res) { + EXPECT_EQ(res, 0) + TH_LOG("waiter failed errno %d: %s", errno, strerror(errno)); + } + + return 0; +} + +TEST(requeue_single) +{ + struct waiter_args wargs = { ._metadata = _metadata, .n_threads = 1 }; + struct futex_thread waiter; + volatile futex_t _f1 = 0; + volatile futex_t f2 = 0; + + f1 = &_f1; + + /* + * Requeue a waiter from f1 to f2, and wake f2. + */ + ASSERT_EQ(futex_thread_create(&waiter, waiterfn, &wargs), 0) + TH_LOG("pthread_create failed"); + + ASSERT_EQ(futex_wait_for_thread(&waiter, _metadata), 0) + TH_LOG("Wait for thread failed"); + + EXPECT_EQ(futex_cmp_requeue(f1, 0, &f2, 0, 1, 0), 1); + EXPECT_EQ(futex_wake(&f2, 1, 0), 1); + + EXPECT_EQ(futex_thread_destroy(&waiter), 0); +} + +TEST(requeue_multiple) +{ + struct waiter_args wargs = { ._metadata = _metadata, .n_threads = 10 }; + struct futex_thread waiter[10]; + volatile futex_t _f1 = 0; + volatile futex_t f2 = 0; + + f1 = &_f1; + + /* + * Create 10 waiters at f1. At futex_requeue, wake 3 and requeue 7. + * At futex_wake, wake INT_MAX (should be exactly 7). + */ + for (int i = 0; i < 10; i++) { + ASSERT_EQ(futex_thread_create(&waiter[i], waiterfn, &wargs), 0) + TH_LOG("pthread_create failed for waiter %d", i); + } + + for (int i = 0; i < 10; i++) { + ASSERT_EQ(futex_wait_for_thread(&waiter[i], _metadata), 0) + TH_LOG("Wait for waiter thread %d failed", i); + } + + EXPECT_EQ(futex_cmp_requeue(f1, 0, &f2, 3, 7, 0), 10); + EXPECT_EQ(futex_wake(&f2, INT_MAX, 0), 7); + + for (int i = 0; i < 10; i++) + EXPECT_EQ(futex_thread_destroy(&waiter[i]), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue_pi.c b/tools/testing/selftests/futex/functional/futex_requeue_pi.c new file mode 100644 index 000000000..65326cbef --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_requeue_pi.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * Copyright © International Business Machines Corp., 2006-2008 + * + * DESCRIPTION + * This test excercises the futex syscall op codes needed for requeuing + * priority inheritance aware POSIX condition variables and mutexes. + * + * AUTHORS + * Sripathi Kodi <sripathik@in.ibm.com> + * Darren Hart <dvhart@linux.intel.com> + * + * HISTORY + * 2008-Jan-13: Initial version by Sripathi Kodi <sripathik@in.ibm.com> + * 2009-Nov-6: futex test adaptation by Darren Hart <dvhart@linux.intel.com> + * + *****************************************************************************/ + +#define _GNU_SOURCE + +#include <errno.h> +#include <limits.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> + +#include "atomic.h" +#include "futextest.h" +#include "kselftest_harness.h" + +#define MAX_WAKE_ITERS 1000 +#define THREAD_MAX 10 +#define SIGNAL_PERIOD_US 100 + +atomic_t waiters_blocked = ATOMIC_INITIALIZER; +atomic_t waiters_woken = ATOMIC_INITIALIZER; + +futex_t f1 = FUTEX_INITIALIZER; +futex_t f2 = FUTEX_INITIALIZER; +futex_t wake_complete = FUTEX_INITIALIZER; + +struct thread_arg { + struct __test_metadata *_metadata; + long id; + struct timespec *timeout; + int lock; + int ret; +}; +#define THREAD_ARG_INITIALIZER { NULL, 0, NULL, 0, 0 } + +FIXTURE(args) +{ +}; + +FIXTURE_SETUP(args) +{ +}; + +FIXTURE_TEARDOWN(args) +{ +}; + +FIXTURE_VARIANT(args) +{ + long timeout_ns; + bool broadcast; + bool owner; + bool locked; +}; + +/* + * For a given timeout value, this macro creates a test input with all the + * possible combinations of valid arguments + */ +#define FIXTURE_VARIANT_ADD_TIMEOUT(timeout) \ + \ +FIXTURE_VARIANT_ADD(args, t_##timeout) \ +{ \ + .timeout_ns = timeout, \ +}; \ + \ +FIXTURE_VARIANT_ADD(args, t_##timeout##_broadcast) \ +{ \ + .timeout_ns = timeout, \ + .broadcast = true, \ +}; \ + \ +FIXTURE_VARIANT_ADD(args, t_##timeout##_broadcast_locked) \ +{ \ + .timeout_ns = timeout, \ + .broadcast = true, \ + .locked = true, \ +}; \ + \ +FIXTURE_VARIANT_ADD(args, t_##timeout##_broadcast_owner) \ +{ \ + .timeout_ns = timeout, \ + .broadcast = true, \ + .owner = true, \ +}; \ + \ +FIXTURE_VARIANT_ADD(args, t_##timeout##_locked) \ +{ \ + .timeout_ns = timeout, \ + .locked = true, \ +}; \ + \ +FIXTURE_VARIANT_ADD(args, t_##timeout##_owner) \ +{ \ + .timeout_ns = timeout, \ + .owner = true, \ +}; \ + +FIXTURE_VARIANT_ADD_TIMEOUT(0); +FIXTURE_VARIANT_ADD_TIMEOUT(5000); +FIXTURE_VARIANT_ADD_TIMEOUT(500000); +FIXTURE_VARIANT_ADD_TIMEOUT(2000000000); + +int create_rt_thread(struct __test_metadata *_metadata, pthread_t *pth, void*(*func)(void *), void *arg, + int policy, int prio) +{ + int ret; + struct sched_param schedp; + pthread_attr_t attr; + + pthread_attr_init(&attr); + memset(&schedp, 0, sizeof(schedp)); + + ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setinheritsched failed"); + + ret = pthread_attr_setschedpolicy(&attr, policy); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedpolicy failed"); + + schedp.sched_priority = prio; + ret = pthread_attr_setschedparam(&attr, &schedp); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedparam failed"); + + ret = pthread_create(pth, &attr, func, arg); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); + + return 0; +} + + +void *waiterfn(void *arg) +{ + struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; + futex_t old_val; + + TH_LOG("Waiter %ld: running", args->id); + /* Each thread sleeps for a different amount of time + * This is to avoid races, because we don't lock the + * external mutex here + */ + usleep(1000 * (long)args->id); + + old_val = f1; + atomic_inc(&waiters_blocked); + TH_LOG("Calling futex_wait_requeue_pi: %p (%u) -> %p", &f1, f1, &f2); + args->ret = futex_wait_requeue_pi(&f1, old_val, &f2, args->timeout, + FUTEX_PRIVATE_FLAG); + + TH_LOG("waiter %ld woke with %d %s", args->id, args->ret, + args->ret < 0 ? strerror(errno) : ""); + atomic_inc(&waiters_woken); + if (args->ret < 0) { + if (args->timeout && errno == ETIMEDOUT) { + args->ret = 0; + } else { + ASSERT_EQ(args->ret, 0) + TH_LOG("futex_wait_requeue_pi failed: %s", strerror(errno)); + } + futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); + } + futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); + + TH_LOG("Waiter %ld: exiting with %d", args->id, args->ret); + pthread_exit((void *)&args->ret); +} + +void *broadcast_wakerfn(void *arg) +{ + struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; + int nr_requeue = INT_MAX; + int task_count = 0; + futex_t old_val; + int nr_wake = 1; + int i = 0; + + TH_LOG("Waker: waiting for waiters to block"); + while (waiters_blocked.val < THREAD_MAX) + usleep(1000); + usleep(1000); + + TH_LOG("Waker: Calling broadcast"); + if (args->lock) { + TH_LOG("Calling FUTEX_LOCK_PI on mutex=%x @ %p", f2, &f2); + futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); + } + continue_requeue: + old_val = f1; + args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, nr_wake, nr_requeue, + FUTEX_PRIVATE_FLAG); + if (args->ret < 0) { + ASSERT_GE(args->ret, 0) + TH_LOG("FUTEX_CMP_REQUEUE_PI failed: %s", strerror(errno)); + } else if (++i < MAX_WAKE_ITERS) { + task_count += args->ret; + if (task_count < THREAD_MAX - waiters_woken.val) + goto continue_requeue; + } else { + ASSERT_TRUE(0) { + TH_LOG("max broadcast iterations (%d) reached with %d/%d tasks woken or requeued", + MAX_WAKE_ITERS, task_count, THREAD_MAX); + } + } + + futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG); + + if (args->lock) + futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); + + if (args->ret > 0) + args->ret = task_count; + + TH_LOG("Waker: exiting with %d", args->ret); + pthread_exit((void *)&args->ret); +} + +void *signal_wakerfn(void *arg) +{ + struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; + unsigned int old_val; + int nr_requeue = 0; + int task_count = 0; + int nr_wake = 1; + int i = 0; + + TH_LOG("Waker: waiting for waiters to block"); + while (waiters_blocked.val < THREAD_MAX) + usleep(1000); + usleep(1000); + + while (task_count < THREAD_MAX && waiters_woken.val < THREAD_MAX) { + TH_LOG("task_count: %d, waiters_woken: %d", + task_count, waiters_woken.val); + if (args->lock) { + TH_LOG("Calling FUTEX_LOCK_PI on mutex=%x @ %p", f2, &f2); + futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); + } + TH_LOG("Waker: Calling signal"); + /* cond_signal */ + old_val = f1; + args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, + nr_wake, nr_requeue, + FUTEX_PRIVATE_FLAG); + if (args->ret < 0) + args->ret = -errno; + TH_LOG("futex: %x", f2); + if (args->lock) { + TH_LOG("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p", + f2, &f2); + futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); + } + TH_LOG("futex: %x", f2); + if (args->ret < 0) { + ASSERT_GE(args->ret, 0) + TH_LOG("FUTEX_CMP_REQUEUE_PI failed: %s", strerror(-args->ret)); + } + + task_count += args->ret; + usleep(SIGNAL_PERIOD_US); + i++; + /* we have to loop at least THREAD_MAX times */ + if (i > MAX_WAKE_ITERS + THREAD_MAX) { + ASSERT_TRUE(0) { + TH_LOG("max signaling iterations (%d) reached, giving up on pending waiters.", + MAX_WAKE_ITERS + THREAD_MAX); + } + } + } + + futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG); + + if (args->ret >= 0) + args->ret = task_count; + + TH_LOG("Waker: exiting with %d", args->ret); + TH_LOG("Waker: waiters_woken: %d", waiters_woken.val); + pthread_exit((void *)&args->ret); +} + +void *third_party_blocker(void *arg) +{ + struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; + int ret2 = 0; + + args->ret = futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); + if (args->ret) + goto out; + args->ret = futex_wait(&wake_complete, wake_complete, NULL, + FUTEX_PRIVATE_FLAG); + ret2 = futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); + + out: + if (args->ret || ret2) { + ASSERT_TRUE(0) + TH_LOG("%s() futex error", __func__); + } + + pthread_exit((void *)&args->ret); +} + +TEST_F(args, futex_requeue_pi) +{ + struct thread_arg blocker_arg = THREAD_ARG_INITIALIZER; + struct thread_arg waker_arg = THREAD_ARG_INITIALIZER; + pthread_t waiter[THREAD_MAX], waker, blocker; + void *(*wakerfn)(void *) = signal_wakerfn; + bool third_party_owner = variant->owner; + long timeout_ns = variant->timeout_ns; + bool broadcast = variant->broadcast; + struct thread_arg args[THREAD_MAX]; + struct timespec ts, *tsp = NULL; + bool lock = variant->locked; + int *waiter_ret, i, ret = 0; + + TH_LOG("Arguments: broadcast=%d locked=%d owner=%d timeout=%ldns", + broadcast, lock, third_party_owner, timeout_ns); + + if (timeout_ns) { + time_t secs; + + TH_LOG("timeout_ns = %ld", timeout_ns); + ret = clock_gettime(CLOCK_MONOTONIC, &ts); + secs = (ts.tv_nsec + timeout_ns) / 1000000000; + ts.tv_nsec = ((int64_t)ts.tv_nsec + timeout_ns) % 1000000000; + ts.tv_sec += secs; + TH_LOG("ts.tv_sec = %ld", ts.tv_sec); + TH_LOG("ts.tv_nsec = %ld", ts.tv_nsec); + tsp = &ts; + } + + if (broadcast) + wakerfn = broadcast_wakerfn; + + if (third_party_owner) { + blocker_arg._metadata = _metadata; + create_rt_thread(_metadata, &blocker, third_party_blocker, + (void *)&blocker_arg, SCHED_FIFO, 1); + } + + atomic_set(&waiters_woken, 0); + for (i = 0; i < THREAD_MAX; i++) { + args[i]._metadata = _metadata; + args[i].id = i; + args[i].timeout = tsp; + TH_LOG("Starting thread %d", i); + create_rt_thread(_metadata, &waiter[i], waiterfn, (void *)&args[i], + SCHED_FIFO, 1); + } + waker_arg._metadata = _metadata; + waker_arg.lock = lock; + create_rt_thread(_metadata, &waker, wakerfn, (void *)&waker_arg, SCHED_FIFO, 1); + + /* Wait for threads to finish */ + /* Store the first error or failure encountered in waiter_ret */ + waiter_ret = &args[0].ret; + for (i = 0; i < THREAD_MAX; i++) + pthread_join(waiter[i], *waiter_ret ? NULL : (void **)&waiter_ret); + + if (third_party_owner) + pthread_join(blocker, NULL); + pthread_join(waker, NULL); + + if (!ret) { + if (*waiter_ret) + ret = *waiter_ret; + else if (waker_arg.ret < 0) + ret = waker_arg.ret; + else if (blocker_arg.ret) + ret = blocker_arg.ret; + } + + EXPECT_EQ(ret, 0) + TH_LOG("Test failed with error code: %d", ret); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue_pi_mismatched_ops.c b/tools/testing/selftests/futex/functional/futex_requeue_pi_mismatched_ops.c new file mode 100644 index 000000000..35bb8a807 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_requeue_pi_mismatched_ops.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * Copyright © International Business Machines Corp., 2009 + * + * DESCRIPTION + * 1. Block a thread using FUTEX_WAIT + * 2. Attempt to use FUTEX_CMP_REQUEUE_PI on the futex from 1. + * 3. The kernel must detect the mismatch and return -EINVAL. + * + * AUTHOR + * Darren Hart <dvhart@linux.intel.com> + * + * HISTORY + * 2009-Nov-9: Initial version by Darren Hart <dvhart@linux.intel.com> + * + *****************************************************************************/ + +#include <errno.h> +#include <getopt.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "futextest.h" +#include "kselftest_harness.h" + +futex_t f1 = FUTEX_INITIALIZER; +futex_t f2 = FUTEX_INITIALIZER; +int child_ret; + +void *blocking_child(void *arg) +{ + struct __test_metadata *_metadata = (struct __test_metadata *)arg; + + child_ret = futex_wait(&f1, f1, NULL, FUTEX_PRIVATE_FLAG); + if (child_ret < 0) { + child_ret = -errno; + ASSERT_EQ(child_ret, 0) + TH_LOG("futex_wait failed: %s", strerror(errno)); + } + return (void *)&child_ret; +} + +TEST(requeue_pi_mismatched_ops) +{ + pthread_t child; + int ret; + + ASSERT_EQ(pthread_create(&child, NULL, blocking_child, _metadata), 0) + TH_LOG("pthread_create failed"); + + /* Allow the child to block in the kernel. */ + sleep(1); + + /* + * The kernel should detect the waiter did not setup the + * q->requeue_pi_key and return -EINVAL. If it does not, + * it likely gave the lock to the child, which is now hung + * in the kernel. + */ + ret = futex_cmp_requeue_pi(&f1, f1, &f2, 1, 0, FUTEX_PRIVATE_FLAG); + if (ret < 0) { + if (errno == EINVAL) { + /* + * The kernel correctly detected the mismatched + * requeue_pi target and aborted. Wake the child with + * FUTEX_WAKE. + */ + ret = futex_wake(&f1, 1, FUTEX_PRIVATE_FLAG); + if (ret == 1) { + ret = 0; + } else if (ret < 0) { + ASSERT_GE(ret, 0) + TH_LOG("futex_wake failed: %s", strerror(errno)); + } else { + ASSERT_TRUE(0) + TH_LOG("futex_wake did not wake the child"); + } + } else { + ASSERT_TRUE(0) + TH_LOG("futex_cmp_requeue_pi failed with unexpected errno: %s", strerror(errno)); + } + } else if (ret > 0) { + EXPECT_EQ(ret, 0) + TH_LOG("futex_cmp_requeue_pi failed to detect the mismatch"); + } else { + ASSERT_TRUE(0) + TH_LOG("futex_cmp_requeue_pi found no waiters"); + } + + pthread_join(child, NULL); + + EXPECT_EQ(ret, 0) + TH_LOG("Test failed: ret=%d", ret); + EXPECT_EQ(child_ret, 0) + TH_LOG("Child failed: child_ret=%d", child_ret); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue_pi_signal_restart.c b/tools/testing/selftests/futex/functional/futex_requeue_pi_signal_restart.c new file mode 100644 index 000000000..4933612a7 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_requeue_pi_signal_restart.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * Copyright © International Business Machines Corp., 2006-2008 + * + * DESCRIPTION + * This test exercises the futex_wait_requeue_pi() signal handling both + * before and after the requeue. The first should be restarted by the + * kernel. The latter should return EWOULDBLOCK to the waiter. + * + * AUTHORS + * Darren Hart <dvhart@linux.intel.com> + * + * HISTORY + * 2008-May-5: Initial version by Darren Hart <dvhart@linux.intel.com> + * + *****************************************************************************/ + +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "atomic.h" +#include "futextest.h" +#include "kselftest_harness.h" + +#define DELAY_US 100 + +futex_t f1 = FUTEX_INITIALIZER; +futex_t f2 = FUTEX_INITIALIZER; +atomic_t requeued = ATOMIC_INITIALIZER; + +int waiter_ret; + +int create_rt_thread(struct __test_metadata *_metadata, pthread_t *pth, void*(*func)(void *), + void *arg, int policy, int prio) +{ + struct sched_param schedp; + pthread_attr_t attr; + int ret; + + pthread_attr_init(&attr); + memset(&schedp, 0, sizeof(schedp)); + + ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setinheritsched failed"); + + ret = pthread_attr_setschedpolicy(&attr, policy); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedpolicy failed"); + + schedp.sched_priority = prio; + ret = pthread_attr_setschedparam(&attr, &schedp); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedparam failed"); + + ret = pthread_create(pth, &attr, func, arg); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); + + return 0; +} + +void handle_signal(int signo) +{ + printf("INFO: signal received %s requeue\n", requeued.val ? "after" : "prior to"); +} + +void *waiterfn(void *arg) +{ + struct __test_metadata *_metadata = (struct __test_metadata *)arg; + unsigned int old_val; + int res; + + TH_LOG("Waiter running"); + TH_LOG("Calling FUTEX_LOCK_PI on f2=%x @ %p", f2, &f2); + old_val = f1; + res = futex_wait_requeue_pi(&f1, old_val, &(f2), NULL, + FUTEX_PRIVATE_FLAG); + if (!requeued.val || errno != EWOULDBLOCK) { + EXPECT_TRUE(0) { + TH_LOG("unexpected return from futex_wait_requeue_pi: %d (%s)", + res, strerror(errno)); + } + TH_LOG("w2:futex: %x", f2); + if (!res) + futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); + } + + pthread_exit(NULL); +} + +TEST(futex_requeue_pi_signal_restart) +{ + unsigned int old_val; + struct sigaction sa; + pthread_t waiter; + int res; + + sa.sa_handler = handle_signal; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + ASSERT_EQ(sigaction(SIGUSR1, &sa, NULL), 0) + TH_LOG("sigaction failed"); + + TH_LOG("m1:f2: %x", f2); + TH_LOG("Creating waiter"); + create_rt_thread(_metadata, &waiter, waiterfn, _metadata, SCHED_FIFO, 1); + + TH_LOG("Calling FUTEX_LOCK_PI on f2=%x @ %p", f2, &f2); + TH_LOG("m2:f2: %x", f2); + futex_lock_pi(&f2, 0, 0, FUTEX_PRIVATE_FLAG); + TH_LOG("m3:f2: %x", f2); + + while (1) { + /* + * signal the waiter before requeue, waiter should automatically + * restart futex_wait_requeue_pi() in the kernel. Wait for the + * waiter to block on f1 again. + */ + TH_LOG("Issuing SIGUSR1 to waiter"); + pthread_kill(waiter, SIGUSR1); + usleep(DELAY_US); + + TH_LOG("Requeueing waiter via FUTEX_CMP_REQUEUE_PI"); + old_val = f1; + res = futex_cmp_requeue_pi(&f1, old_val, &(f2), 1, 0, + FUTEX_PRIVATE_FLAG); + /* + * If res is non-zero, we either requeued the waiter or hit an + * error, break out and handle it. If it is zero, then the + * signal may have hit before the waiter was blocked on f1. + * Try again. + */ + if (res > 0) { + atomic_set(&requeued, 1); + break; + } else if (res < 0) { + ASSERT_GE(res, 0) + TH_LOG("FUTEX_CMP_REQUEUE_PI failed: %s", strerror(errno)); + } + } + TH_LOG("m4:f2: %x", f2); + + /* + * Signal the waiter after requeue, waiter should return from + * futex_wait_requeue_pi() with EWOULDBLOCK. Join the thread here so the + * futex_unlock_pi() can't happen before the signal wakeup is detected + * in the kernel. + */ + TH_LOG("Issuing SIGUSR1 to waiter"); + pthread_kill(waiter, SIGUSR1); + TH_LOG("Waiting for waiter to return"); + pthread_join(waiter, NULL); + + TH_LOG("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p", f2, &f2); + futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); + TH_LOG("m5:f2: %x", f2); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait.c b/tools/testing/selftests/futex/functional/futex_wait.c new file mode 100644 index 000000000..5292f5d2c --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_wait.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright Collabora Ltd., 2021 + * + * futex cmp requeue test by André Almeida <andrealmeid@collabora.com> + */ + +#include <fcntl.h> +#include <pthread.h> +#include <stdlib.h> +#include <sys/shm.h> +#include <sys/mman.h> + +#include "futextest.h" +#include "kselftest_harness.h" + +#define timeout_ns 30000000 +#define WAKE_WAIT_US 10000 +#define SHM_PATH "futex_shm_file" + +void *futex; + +struct waiter_args { + struct __test_metadata *_metadata; + unsigned int flags; +}; + +static void *waiterfn(void *arg) +{ + struct waiter_args *args = (struct waiter_args *)arg; + struct __test_metadata *_metadata = args->_metadata; + struct timespec to; + int res; + + to.tv_sec = 0; + to.tv_nsec = timeout_ns; + + res = futex_wait(futex, 0, &to, args->flags); + if (res) { + EXPECT_EQ(res, 0) + TH_LOG("waiter failed errno %d: %s", errno, strerror(errno)); + } + + free(args); + return NULL; +} + +TEST(private_futex) +{ + struct waiter_args *args = malloc(sizeof(*args)); + u_int32_t f_private = 0; + pthread_t waiter; + int res; + + args->_metadata = _metadata; + args->flags = FUTEX_PRIVATE_FLAG; + futex = &f_private; + + /* Testing a private futex */ + TH_LOG("Calling private futex_wait on futex: %p", futex); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0) + TH_LOG("pthread_create failed"); + + usleep(WAKE_WAIT_US); + + TH_LOG("Calling private futex_wake on futex: %p", futex); + res = futex_wake(futex, 1, FUTEX_PRIVATE_FLAG); + EXPECT_EQ(res, 1) + TH_LOG("futex_wake private returned: %d %s", res, res < 0 ? strerror(errno) : ""); + + pthread_join(waiter, NULL); +} + +TEST(anon_page) +{ + struct waiter_args *args = malloc(sizeof(*args)); + u_int32_t *shared_data; + pthread_t waiter; + int res, shm_id; + + args->_metadata = _metadata; + args->flags = 0; + + /* Testing an anon page shared memory */ + shm_id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666); + if (shm_id < 0) { + if (errno == ENOSYS) { + free(args); + SKIP(return, "shmget syscall not supported"); + } + ASSERT_GE(shm_id, 0) + TH_LOG("shmget failed: %s", strerror(errno)); + } + + shared_data = shmat(shm_id, NULL, 0); + if (shared_data == (void *)-1) { + free(args); + ASSERT_NE(shared_data, (void *)-1) + TH_LOG("shmat failed: %s", strerror(errno)); + } + + *shared_data = 0; + futex = shared_data; + + TH_LOG("Calling shared (page anon) futex_wait on futex: %p", futex); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0) + TH_LOG("pthread_create failed"); + + usleep(WAKE_WAIT_US); + + TH_LOG("Calling shared (page anon) futex_wake on futex: %p", futex); + res = futex_wake(futex, 1, 0); + EXPECT_EQ(res, 1) { + TH_LOG("futex_wake shared (page anon) returned: %d %s", + res, res < 0 ? strerror(errno) : ""); + } + + pthread_join(waiter, NULL); + shmdt(shared_data); +} + +TEST(file_backed) +{ + struct waiter_args *args = malloc(sizeof(*args)); + u_int32_t f_private = 0; + pthread_t waiter; + int res, fd; + void *shm; + + args->_metadata = _metadata; + args->flags = 0; + + /* Testing a file backed shared memory */ + fd = open(SHM_PATH, O_RDWR | O_CREAT, 0600); + if (fd < 0) { + free(args); + ASSERT_GE(fd, 0) + TH_LOG("open failed: %s", strerror(errno)); + } + + if (ftruncate(fd, sizeof(f_private))) { + free(args); + close(fd); + ASSERT_TRUE(0) + TH_LOG("ftruncate failed: %s", strerror(errno)); + } + + shm = mmap(NULL, sizeof(f_private), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (shm == MAP_FAILED) { + free(args); + close(fd); + ASSERT_NE(shm, MAP_FAILED) + TH_LOG("mmap failed: %s", strerror(errno)); + } + + memcpy(shm, &f_private, sizeof(f_private)); + + futex = shm; + + TH_LOG("Calling shared (file backed) futex_wait on futex: %p", futex); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0) + TH_LOG("pthread_create failed"); + + usleep(WAKE_WAIT_US); + + TH_LOG("Calling shared (file backed) futex_wake on futex: %p", futex); + res = futex_wake(shm, 1, 0); + EXPECT_EQ(res, 1) { + TH_LOG("futex_wake shared (file backed) returned: %d %s", + res, res < 0 ? strerror(errno) : ""); + } + + pthread_join(waiter, NULL); + munmap(shm, sizeof(f_private)); + remove(SHM_PATH); + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait_private_mapped_file.c b/tools/testing/selftests/futex/functional/futex_wait_private_mapped_file.c new file mode 100644 index 000000000..a9f7a02e3 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_wait_private_mapped_file.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * Copyright FUJITSU LIMITED 2010 + * Copyright KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> + * + * DESCRIPTION + * Internally, Futex has two handling mode, anon and file. The private file + * mapping is special. At first it behave as file, but after write anything + * it behave as anon. This test is intent to test such case. + * + * AUTHOR + * KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> + * + * HISTORY + * 2010-Jan-6: Initial version by KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> + * + *****************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <syscall.h> +#include <unistd.h> +#include <errno.h> +#include <linux/futex.h> +#include <pthread.h> +#include <libgen.h> +#include <signal.h> +#include <string.h> + +#include "futextest.h" +#include "kselftest_harness.h" + +#define PAGE_SZ 4096 + +char pad[PAGE_SZ] = {1}; +futex_t val = 1; +char pad2[PAGE_SZ] = {1}; + +#define WAKE_WAIT_US 3000000 +struct timespec wait_timeout = { .tv_sec = 5, .tv_nsec = 0}; + +void *thr_futex_wait(void *arg) +{ + struct __test_metadata *_metadata = (struct __test_metadata *)arg; + int ret; + + TH_LOG("futex wait"); + ret = futex_wait(&val, 1, &wait_timeout, 0); + if (ret && errno != EWOULDBLOCK && errno != ETIMEDOUT) { + ASSERT_TRUE(0) + TH_LOG("futex error: %s", strerror(errno)); + } + + if (ret && errno == ETIMEDOUT) { + ASSERT_TRUE(0) + TH_LOG("waiter timedout"); + } + + TH_LOG("futex_wait: ret = %d, errno = %d", ret, errno); + + return NULL; +} + +TEST(wait_private_mapped_file) +{ + pthread_t thr; + int res; + + res = pthread_create(&thr, NULL, thr_futex_wait, _metadata); + ASSERT_EQ(res, 0) + TH_LOG("pthread_create error"); + + TH_LOG("wait a while"); + usleep(WAKE_WAIT_US); + val = 2; + res = futex_wake(&val, 1, 0); + TH_LOG("futex_wake %d", res); + EXPECT_EQ(res, 1) + TH_LOG("FUTEX_WAKE didn't find the waiting thread"); + + TH_LOG("join"); + pthread_join(thr, NULL); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait_timeout.c b/tools/testing/selftests/futex/functional/futex_wait_timeout.c new file mode 100644 index 000000000..6e6e770fe --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_wait_timeout.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * Copyright © International Business Machines Corp., 2009 + * + * DESCRIPTION + * Block on a futex and wait for timeout. + * + * AUTHOR + * Darren Hart <dvhart@linux.intel.com> + * + * HISTORY + * 2009-Nov-6: Initial version by Darren Hart <dvhart@linux.intel.com> + * 2021-Apr-26: More test cases by André Almeida <andrealmeid@collabora.com> + * + *****************************************************************************/ + +#include <pthread.h> + +#include "futextest.h" +#include "futex2test.h" +#include "kselftest_harness.h" + +static long timeout_ns = 100000; /* 100us default timeout */ +static futex_t futex_pi; +static pthread_barrier_t barrier; + +/* + * Get a PI lock and hold it forever, so the main thread lock_pi will block + * and we can test the timeout + */ +void *get_pi_lock(void *arg) +{ + struct __test_metadata *_metadata = (struct __test_metadata *)arg; + int ret; + volatile futex_t lock = 0; + + ret = futex_lock_pi(&futex_pi, NULL, 0, 0); + ASSERT_EQ(ret, 0) + TH_LOG("futex_lock_pi failed"); + + pthread_barrier_wait(&barrier); + + /* Blocks forever */ + ret = futex_wait(&lock, 0, NULL, 0); + ASSERT_TRUE(0) + TH_LOG("futex_wait returned unexpectedly: %d", ret); + + return NULL; +} + +#define TEST_TIMEOUT(_res, _test_name, _err) do { \ + if ((_res) < 0 && errno == ENOSYS && (_err) != ENOSYS) { \ + SKIP(return, "%s is not supported (ENOSYS)", _test_name); \ + } \ + EXPECT_EQ((_res), -1) \ + TH_LOG("%s returned unexpected result: %d", _test_name, (_res));\ + if ((_res) == -1) { \ + EXPECT_EQ(errno, (_err)) { \ + TH_LOG("%s returned unexpected errno: %d (expected %d)",\ + _test_name, errno, (_err)); \ + } \ + } \ +} while (0) + +#define GET_ABS_TIMEOUT(_clockid, _to, _timeout_ns) do { \ + ASSERT_EQ(clock_gettime((_clockid), (_to)), 0) \ + TH_LOG("clock_gettime failed"); \ + (_to)->tv_nsec += (_timeout_ns); \ + if ((_to)->tv_nsec >= 1000000000) { \ + (_to)->tv_sec++; \ + (_to)->tv_nsec -= 1000000000; \ + } \ +} while (0) + +TEST(wait_bitset) +{ + futex_t f1 = FUTEX_INITIALIZER; + struct timespec to; + int res; + + /* initialize relative timeout */ + to.tv_sec = 0; + to.tv_nsec = timeout_ns; + + res = futex_wait(&f1, f1, &to, 0); + TEST_TIMEOUT(res, "futex_wait relative", ETIMEDOUT); + + /* FUTEX_WAIT_BITSET with CLOCK_REALTIME */ + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); + res = futex_wait_bitset(&f1, f1, &to, 1, FUTEX_CLOCK_REALTIME); + TEST_TIMEOUT(res, "futex_wait_bitset realtime", ETIMEDOUT); + + /* FUTEX_WAIT_BITSET with CLOCK_MONOTONIC */ + GET_ABS_TIMEOUT(CLOCK_MONOTONIC, &to, timeout_ns); + res = futex_wait_bitset(&f1, f1, &to, 1, 0); + TEST_TIMEOUT(res, "futex_wait_bitset monotonic", ETIMEDOUT); +} + +TEST(requeue_pi) +{ + futex_t f1 = FUTEX_INITIALIZER; + struct timespec to; + int res; + + /* FUTEX_WAIT_REQUEUE_PI with CLOCK_REALTIME */ + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); + res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, FUTEX_CLOCK_REALTIME); + TEST_TIMEOUT(res, "futex_wait_requeue_pi realtime", ETIMEDOUT); + + /* FUTEX_WAIT_REQUEUE_PI with CLOCK_MONOTONIC */ + GET_ABS_TIMEOUT(CLOCK_MONOTONIC, &to, timeout_ns); + res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, 0); + TEST_TIMEOUT(res, "futex_wait_requeue_pi monotonic", ETIMEDOUT); +} + +TEST(lock_pi) +{ + struct timespec to; + pthread_t thread; + int res; + + /* Create a thread that will lock forever so any waiter will timeout */ + pthread_barrier_init(&barrier, NULL, 2); + ASSERT_EQ(pthread_create(&thread, NULL, get_pi_lock, _metadata), 0) + TH_LOG("pthread_create failed"); + + /* Wait until the other thread calls futex_lock_pi() */ + pthread_barrier_wait(&barrier); + pthread_barrier_destroy(&barrier); + + /* + * FUTEX_LOCK_PI with CLOCK_REALTIME + * Due to historical reasons, FUTEX_LOCK_PI supports only realtime + * clock, but requires the caller to not set CLOCK_REALTIME flag. + * + * If you call FUTEX_LOCK_PI with a monotonic clock, it'll be + * interpreted as a realtime clock, and (unless you mess your machine's + * time or your time machine) the monotonic clock value is always + * smaller than realtime and the syscall will timeout immediately. + */ + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); + res = futex_lock_pi(&futex_pi, &to, 0, 0); + TEST_TIMEOUT(res, "futex_lock_pi realtime", ETIMEDOUT); + + /* Test operations that don't support FUTEX_CLOCK_REALTIME */ + res = futex_lock_pi(&futex_pi, NULL, 0, FUTEX_CLOCK_REALTIME); + TEST_TIMEOUT(res, "futex_lock_pi invalid timeout flag", ENOSYS); +} + +TEST(waitv) +{ + futex_t f1 = FUTEX_INITIALIZER; + struct futex_waitv waitv = { + .uaddr = (uintptr_t)&f1, + .val = f1, + .flags = FUTEX_32, + .__reserved = 0, + }; + struct timespec to; + int res; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + /* futex_waitv with CLOCK_MONOTONIC */ + GET_ABS_TIMEOUT(CLOCK_MONOTONIC, &to, timeout_ns); + res = futex_waitv(&waitv, 1, 0, &to, CLOCK_MONOTONIC); + TEST_TIMEOUT(res, "futex_waitv monotonic", ETIMEDOUT); + + /* futex_waitv with CLOCK_REALTIME */ + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); + res = futex_waitv(&waitv, 1, 0, &to, CLOCK_REALTIME); + TEST_TIMEOUT(res, "futex_waitv realtime", ETIMEDOUT); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait_uninitialized_heap.c b/tools/testing/selftests/futex/functional/futex_wait_uninitialized_heap.c new file mode 100644 index 000000000..bbffc23e0 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_wait_uninitialized_heap.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * Copyright FUJITSU LIMITED 2010 + * Copyright KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> + * + * DESCRIPTION + * Wait on uninitialized heap. It shold be zero and FUTEX_WAIT should + * return immediately. This test is intent to test zero page handling in + * futex. + * + * AUTHOR + * KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> + * + * HISTORY + * 2010-Jan-6: Initial version by KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> + * + *****************************************************************************/ + +#include <errno.h> +#include <libgen.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/futex.h> +#include <sys/mman.h> +#include <syscall.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "futextest.h" +#include "kselftest_harness.h" + +#define WAIT_US 5000000 + +static int child_blocked = 1; +static bool child_ret; +void *buf; + +void *wait_thread(void *arg) +{ + struct __test_metadata *_metadata = (struct __test_metadata *)arg; + int res; + + child_ret = true; + res = futex_wait(buf, 1, NULL, 0); + child_blocked = 0; + + if (res != 0 && errno != EWOULDBLOCK) { + EXPECT_EQ(res, 0) + TH_LOG("futex failure: %s", strerror(errno)); + child_ret = false; + } + pthread_exit(NULL); +} + +TEST(futex_wait_uninitialized_heap) +{ + long page_size; + pthread_t thr; + int ret; + + page_size = sysconf(_SC_PAGESIZE); + + buf = mmap(NULL, page_size, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); + ASSERT_NE(buf, MAP_FAILED) + TH_LOG("mmap failed: %s", strerror(errno)); + + ret = pthread_create(&thr, NULL, wait_thread, _metadata); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); + + TH_LOG("waiting %dus for child to return", WAIT_US); + usleep(WAIT_US); + + EXPECT_EQ(child_blocked, 0) + TH_LOG("child blocked in kernel"); + EXPECT_TRUE(child_ret) + TH_LOG("child error"); + + pthread_join(thr, NULL); + munmap(buf, page_size); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait_wouldblock.c b/tools/testing/selftests/futex/functional/futex_wait_wouldblock.c new file mode 100644 index 000000000..ab039dda3 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_wait_wouldblock.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * Copyright © International Business Machines Corp., 2009 + * + * DESCRIPTION + * Test if FUTEX_WAIT op returns -EWOULDBLOCK if the futex value differs + * from the expected one. + * + * AUTHOR + * Gowrishankar <gowrishankar.m@in.ibm.com> + * + * HISTORY + * 2009-Nov-14: Initial version by Gowrishankar <gowrishankar.m@in.ibm.com> + * + *****************************************************************************/ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "futextest.h" +#include "futex2test.h" +#include "kselftest_harness.h" + +#define timeout_ns 100000 + + +TEST(futex_wait_wouldblock) +{ + struct timespec to = {.tv_sec = 0, .tv_nsec = timeout_ns}; + futex_t f1 = FUTEX_INITIALIZER; + int res; + + TH_LOG("Calling futex_wait on f1: %u @ %p with val=%u", f1, &f1, f1+1); + res = futex_wait(&f1, f1+1, &to, FUTEX_PRIVATE_FLAG); + EXPECT_EQ(res, -1) + TH_LOG("futex_wait returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EWOULDBLOCK) + TH_LOG("futex_wait returned unexpected errno: %d", errno); + } +} + +TEST(futex_waitv_wouldblock) +{ + struct timespec to = {.tv_sec = 0, .tv_nsec = timeout_ns}; + futex_t f1 = FUTEX_INITIALIZER; + struct futex_waitv waitv = { + .uaddr = (uintptr_t)&f1, + .val = f1 + 1, + .flags = FUTEX_32, + .__reserved = 0, + }; + int res; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("clock_gettime failed"); + + to.tv_nsec += timeout_ns; + + if (to.tv_nsec >= 1000000000) { + to.tv_sec++; + to.tv_nsec -= 1000000000; + } + + TH_LOG("Calling futex_waitv on f1: %u @ %p with val=%u", f1, &f1, f1+1); + res = futex_waitv(&waitv, 1, 0, &to, CLOCK_MONOTONIC); + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EWOULDBLOCK) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); + } +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_waitv.c b/tools/testing/selftests/futex/functional/futex_waitv.c new file mode 100644 index 000000000..4858d5fae --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_waitv.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * futex_waitv() test by André Almeida <andrealmeid@collabora.com> + * + * Copyright 2021 Collabora Ltd. + */ + +#include <errno.h> +#include <error.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <pthread.h> +#include <stdint.h> +#include <sys/shm.h> + +#include "futextest.h" +#include "futex2test.h" +#include "kselftest_harness.h" + +#define WAKE_WAIT_US 10000 +#define NR_FUTEXES 30 +static struct futex_waitv waitv[NR_FUTEXES]; +u_int32_t futexes[NR_FUTEXES] = {0}; + + +void *waiterfn(void *arg) +{ + struct __test_metadata *_metadata = (struct __test_metadata *)arg; + struct timespec to; + int res; + + /* setting absolute timeout for futex2 */ + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); + + to.tv_sec++; + + res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); + if (res < 0) { + EXPECT_EQ(res, NR_FUTEXES - 1) + TH_LOG("futex_waitv failed: %s", strerror(errno)); + } else { + EXPECT_EQ(res, NR_FUTEXES - 1) + TH_LOG("futex_waitv returned %d, expected %d", res, NR_FUTEXES - 1); + } + + return NULL; +} + +TEST(private_waitv) +{ + pthread_t waiter; + int res, i; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + for (i = 0; i < NR_FUTEXES; i++) { + waitv[i].uaddr = (uintptr_t)&futexes[i]; + waitv[i].flags = FUTEX_32 | FUTEX_PRIVATE_FLAG; + waitv[i].val = 0; + waitv[i].__reserved = 0; + } + + /* Private waitv */ + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, _metadata), 0) + TH_LOG("pthread_create failed"); + + usleep(WAKE_WAIT_US); + + res = futex_wake(u64_to_ptr(waitv[NR_FUTEXES - 1].uaddr), 1, FUTEX_PRIVATE_FLAG); + EXPECT_EQ(res, 1) + TH_LOG("futex_wake private returned: %d %s", res, res < 0 ? strerror(errno) : ""); +} + +TEST(shared_waitv) +{ + pthread_t waiter; + int res, i; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + /* Shared waitv */ + for (i = 0; i < NR_FUTEXES; i++) { + int shm_id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666); + + if (shm_id < 0) { + if (errno == ENOSYS) + SKIP(return, "shmget syscall not supported"); + ASSERT_GE(shm_id, 0) + TH_LOG("shmget failed"); + } + + unsigned int *shared_data = shmat(shm_id, NULL, 0); + + *shared_data = 0; + waitv[i].uaddr = (uintptr_t)shared_data; + waitv[i].flags = FUTEX_32; + waitv[i].val = 0; + waitv[i].__reserved = 0; + } + + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, _metadata), 0) + TH_LOG("pthread_create failed"); + + usleep(WAKE_WAIT_US); + + res = futex_wake(u64_to_ptr(waitv[NR_FUTEXES - 1].uaddr), 1, 0); + EXPECT_EQ(res, 1) + TH_LOG("futex_wake shared returned: %d %s", res, res < 0 ? strerror(errno) : ""); + + for (i = 0; i < NR_FUTEXES; i++) + shmdt(u64_to_ptr(waitv[i].uaddr)); +} + +TEST(invalid_flag) +{ + struct timespec to; + int res; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + /* Testing a waiter without FUTEX_32 flag */ + waitv[0].flags = FUTEX_PRIVATE_FLAG; + + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); + + to.tv_sec++; + + res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); + } +} + +TEST(unaligned_address) +{ + struct timespec to; + int res; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + /* Testing a waiter with an unaligned address */ + waitv[0].flags = FUTEX_PRIVATE_FLAG | FUTEX_32; + waitv[0].uaddr = 1; + + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); + + to.tv_sec++; + + res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); + } +} + +TEST(null_address) +{ + struct timespec to; + int res; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + /* Testing a NULL address for waiters.uaddr */ + waitv[0].uaddr = 0x00000000; + + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); + + to.tv_sec++; + + res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); + } + + /* Testing a NULL address for *waiters */ + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); + + to.tv_sec++; + + res = futex_waitv(NULL, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); + } +} + +TEST(invalid_clockid) +{ + struct timespec to; + int res; + + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + /* Testing an invalid clockid */ + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); + + to.tv_sec++; + + res = futex_waitv(NULL, NR_FUTEXES, 0, &to, CLOCK_TAI); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); + } +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/robust_list.c b/tools/testing/selftests/futex/functional/robust_list.c new file mode 100644 index 000000000..87217c549 --- /dev/null +++ b/tools/testing/selftests/futex/functional/robust_list.c @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2025 Igalia S.L. + * + * Robust list test by André Almeida <andrealmeid@igalia.com> + * + * The robust list uAPI allows userspace to create "robust" locks, in the sense + * that if the lock holder thread dies, the remaining threads that are waiting + * for the lock won't block forever, waiting for a lock that will never be + * released. + * + * This is achieve by userspace setting a list where a thread can enter all the + * locks (futexes) that it is holding. The robust list is a linked list, and + * userspace register the start of the list with the syscall set_robust_list(). + * If such thread eventually dies, the kernel will walk this list, waking up one + * thread waiting for each futex and marking the futex word with the flag + * FUTEX_OWNER_DIED. + * + * See also + * man set_robust_list + * Documententation/locking/robust-futex-ABI.rst + * Documententation/locking/robust-futexes.rst + */ + +#define _GNU_SOURCE + +#include "futextest.h" +#include "kselftest_harness.h" + +#include <dlfcn.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> +#include <stdatomic.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/auxv.h> +#include <sys/mman.h> +#include <sys/wait.h> + +#define STACK_SIZE (1024 * 1024) +#define FUTEX_TIMEOUT 3 +#define SLEEP_US 100 + +#if __SIZEOF_LONG__ == 8 +# define BUILD_64 +#endif + +static pthread_barrier_t barrier, barrier2; + +static int set_robust_list(struct robust_list_head *head, size_t len) +{ + return syscall(SYS_set_robust_list, head, len); +} + +static int get_robust_list(int pid, struct robust_list_head **head, size_t *len_ptr) +{ + return syscall(SYS_get_robust_list, pid, head, len_ptr); +} + +static int sys_futex_robust_unlock(_Atomic(uint32_t) *uaddr, unsigned int op, int val, + void *list_op_pending, unsigned int val3) +{ + return syscall(SYS_futex, uaddr, op, val, NULL, list_op_pending, val3, 0); +} + +/* + * Basic lock struct, contains just the futex word and the robust list element + * Real implementations have also a *prev to easily walk in the list + */ +typedef _Atomic(unsigned int) atomic_futex_t; + +struct lock_struct { + atomic_futex_t futex; + struct robust_list list; +}; + +struct child_args { + struct __test_metadata *_metadata; + void *arg; +}; + +/* + * Helper function to spawn a child thread. Returns -1 on error, pid on success + */ +static int create_child(struct __test_metadata *_metadata, int (*fn)(void *arg), void *arg) +{ + struct child_args *cargs = malloc(sizeof(*cargs)); + char *stack; + pid_t pid; + + if (!cargs) + return -1; + cargs->_metadata = _metadata; + cargs->arg = arg; + + stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stack == MAP_FAILED) { + free(cargs); + return -1; + } + + stack += STACK_SIZE; + + pid = clone(fn, stack, CLONE_VM | SIGCHLD, cargs); + if (pid == -1) { + free(cargs); + return -1; + } + + return pid; +} + +/* + * Helper function to prepare and register a robust list + */ +static int set_list(struct robust_list_head *head) +{ + int ret; + + ret = set_robust_list(head, sizeof(*head)); + if (ret) + return ret; + + head->futex_offset = (size_t) offsetof(struct lock_struct, futex) - + (size_t) offsetof(struct lock_struct, list); + head->list.next = &head->list; + head->list_op_pending = NULL; + + return 0; +} + +/* + * A basic (and incomplete) mutex lock function with robustness + */ +static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, bool error_inject) +{ + atomic_futex_t *futex = &lock->futex; + unsigned int zero = 0; + pid_t tid = gettid(); + int ret = -1; + + /* + * Set list_op_pending before starting the lock, so the kernel can catch + * the case where the thread died during the lock operation + */ + head->list_op_pending = &lock->list; + + if (atomic_compare_exchange_strong(futex, &zero, tid)) { + /* + * We took the lock, insert it in the robust list + */ + struct robust_list *list = &head->list; + + /* Error injection to test list_op_pending */ + if (error_inject) + return 0; + + while (list->next != &head->list) + list = list->next; + + list->next = &lock->list; + lock->list.next = &head->list; + + ret = 0; + } else { + /* + * We didn't take the lock, wait until the owner wakes (or dies) + */ + struct timespec to; + + to.tv_sec = FUTEX_TIMEOUT; + to.tv_nsec = 0; + + tid = atomic_load(futex); + /* Kernel ignores futexes without the waiters flag */ + tid |= FUTEX_WAITERS; + atomic_store(futex, tid); + + ret = futex_wait((futex_t *) futex, tid, &to, 0); + + /* + * A real mutex_lock() implementation would loop here to finally + * take the lock. We don't care about that, so we stop here. + */ + } + + head->list_op_pending = NULL; + + return ret; +} + +/* + * This child thread will succeed taking the lock, and then will exit holding it + */ +static int child_fn_lock(void *arg) +{ + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct lock_struct *lock = cargs->arg; + struct robust_list_head head; + int ret; + + free(cargs); + + ret = set_list(&head); + ASSERT_EQ(ret, 0) + TH_LOG("set_robust_list error"); + + ret = mutex_lock(lock, &head, false); + ASSERT_EQ(ret, 0) + TH_LOG("mutex_lock error"); + + pthread_barrier_wait(&barrier); + + /* + * There's a race here: the parent thread needs to be inside + * futex_wait() before the child thread dies, otherwise it will miss the + * wakeup from handle_futex_death() that this child will emit. We wait a + * little bit just to make sure that this happens. + */ + usleep(SLEEP_US); + + return 0; +} + +/* + * Spawns a child thread that will set a robust list, take the lock, register it + * in the robust list and die. The parent thread will wait on this futex, and + * should be waken up when the child exits. + */ +TEST(test_robustness) +{ + struct lock_struct lock = { .futex = 0 }; + atomic_futex_t *futex = &lock.futex; + struct robust_list_head head; + int ret, pid, wstatus; + + ret = set_list(&head); + ASSERT_EQ(ret, 0); + + /* + * Lets use a barrier to ensure that the child thread takes the lock + * before the parent + */ + ret = pthread_barrier_init(&barrier, NULL, 2); + ASSERT_EQ(ret, 0); + + pid = create_child(_metadata, &child_fn_lock, &lock); + ASSERT_NE(pid, -1); + + pthread_barrier_wait(&barrier); + ret = mutex_lock(&lock, &head, false); + + /* + * futex_wait() should return 0 and the futex word should be marked with + * FUTEX_OWNER_DIED + */ + ASSERT_EQ(ret, 0); + + ASSERT_TRUE(*futex & FUTEX_OWNER_DIED); + + wait(&wstatus); + pthread_barrier_destroy(&barrier); + + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); +} + +/* + * The only valid value for len is sizeof(*head) + */ +TEST(test_set_robust_list_invalid_size) +{ + struct robust_list_head head; + size_t head_size = sizeof(head); + int ret; + + ret = set_robust_list(&head, head_size); + ASSERT_EQ(ret, 0); + + ret = set_robust_list(&head, head_size * 2); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + ret = set_robust_list(&head, head_size - 1); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + ret = set_robust_list(&head, 0); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); +} + +/* + * Test get_robust_list with pid = 0, getting the list of the running thread + */ +TEST(test_get_robust_list_self) +{ + struct robust_list_head head, head2, *get_head; + size_t head_size = sizeof(head), len_ptr; + int ret; + + ret = set_robust_list(&head, head_size); + ASSERT_EQ(ret, 0); + + ret = get_robust_list(0, &get_head, &len_ptr); + ASSERT_EQ(ret, 0); + ASSERT_EQ(get_head, &head); + ASSERT_EQ(head_size, len_ptr); + + ret = set_robust_list(&head2, head_size); + ASSERT_EQ(ret, 0); + + ret = get_robust_list(0, &get_head, &len_ptr); + ASSERT_EQ(ret, 0); + ASSERT_EQ(get_head, &head2); + ASSERT_EQ(head_size, len_ptr); +} + +static int child_list(void *arg) +{ + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct robust_list_head *head = cargs->arg; + int ret; + + free(cargs); + + ret = set_robust_list(head, sizeof(*head)); + ASSERT_EQ(ret, 0) + TH_LOG("set_robust_list error"); + + /* + * After setting the list head, wait until the main thread can call + * get_robust_list() for this thread before exiting. + */ + pthread_barrier_wait(&barrier); + pthread_barrier_wait(&barrier2); + + return 0; +} + +/* + * Test get_robust_list from another thread. We use two barriers here to ensure + * that: + * 1) the child thread set the list before we try to get it from the + * parent + * 2) the child thread still alive when we try to get the list from it + */ +TEST(test_get_robust_list_child) +{ + struct robust_list_head head, *get_head; + int ret, wstatus; + size_t len_ptr; + pid_t tid; + + ret = pthread_barrier_init(&barrier, NULL, 2); + ret = pthread_barrier_init(&barrier2, NULL, 2); + ASSERT_EQ(ret, 0); + + tid = create_child(_metadata, &child_list, &head); + ASSERT_NE(tid, -1); + + pthread_barrier_wait(&barrier); + + ret = get_robust_list(tid, &get_head, &len_ptr); + ASSERT_EQ(ret, 0); + ASSERT_EQ(&head, get_head); + + pthread_barrier_wait(&barrier2); + + wait(&wstatus); + pthread_barrier_destroy(&barrier); + pthread_barrier_destroy(&barrier2); + + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); +} + +static int child_fn_lock_with_error(void *arg) +{ + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct lock_struct *lock = cargs->arg; + struct robust_list_head head; + int ret; + + free(cargs); + + ret = set_list(&head); + ASSERT_EQ(ret, 0) + TH_LOG("set_robust_list error"); + + ret = mutex_lock(lock, &head, true); + ASSERT_EQ(ret, 0) + TH_LOG("mutex_lock error"); + + pthread_barrier_wait(&barrier); + + /* See comment at child_fn_lock() */ + usleep(SLEEP_US); + + return 0; +} + +/* + * Same as robustness test, but inject an error where the mutex_lock() exits + * earlier, just after setting list_op_pending and taking the lock, to test the + * list_op_pending mechanism + */ +TEST(test_set_list_op_pending) +{ + struct lock_struct lock = { .futex = 0 }; + atomic_futex_t *futex = &lock.futex; + struct robust_list_head head; + int ret, wstatus; + + ret = set_list(&head); + ASSERT_EQ(ret, 0); + + ret = pthread_barrier_init(&barrier, NULL, 2); + ASSERT_EQ(ret, 0); + + ret = create_child(_metadata, &child_fn_lock_with_error, &lock); + ASSERT_NE(ret, -1); + + pthread_barrier_wait(&barrier); + ret = mutex_lock(&lock, &head, false); + + ASSERT_EQ(ret, 0); + + ASSERT_TRUE(*futex & FUTEX_OWNER_DIED); + + wait(&wstatus); + pthread_barrier_destroy(&barrier); + + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); +} + +#define CHILD_NR 10 + +static int child_lock_holder(void *arg) +{ + struct child_args *cargs = arg; + struct lock_struct *locks = cargs->arg; + struct robust_list_head head; + int i; + + free(cargs); + + set_list(&head); + + for (i = 0; i < CHILD_NR; i++) { + locks[i].futex = 0; + mutex_lock(&locks[i], &head, false); + } + + pthread_barrier_wait(&barrier); + pthread_barrier_wait(&barrier2); + + /* See comment at child_fn_lock() */ + usleep(SLEEP_US); + + return 0; +} + +static int child_wait_lock(void *arg) +{ + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct lock_struct *lock = cargs->arg; + struct robust_list_head head; + int ret; + + free(cargs); + + pthread_barrier_wait(&barrier2); + ret = mutex_lock(lock, &head, false); + ASSERT_EQ(ret, 0) + TH_LOG("mutex_lock error"); + + ASSERT_TRUE(lock->futex & FUTEX_OWNER_DIED) + TH_LOG("futex not marked with FUTEX_OWNER_DIED"); + + return 0; +} + +/* + * Test a robust list of more than one element. All the waiters should wake when + * the holder dies + */ +TEST(test_robust_list_multiple_elements) +{ + struct lock_struct locks[CHILD_NR]; + pid_t pids[CHILD_NR + 1]; + int i, ret, wstatus; + + ret = pthread_barrier_init(&barrier, NULL, 2); + ASSERT_EQ(ret, 0); + ret = pthread_barrier_init(&barrier2, NULL, CHILD_NR + 1); + ASSERT_EQ(ret, 0); + + pids[0] = create_child(_metadata, &child_lock_holder, &locks); + ASSERT_NE(pids[0], -1); + + /* Wait until the locker thread takes the look */ + pthread_barrier_wait(&barrier); + + for (i = 0; i < CHILD_NR; i++) { + pids[i+1] = create_child(_metadata, &child_wait_lock, &locks[i]); + ASSERT_NE(pids[i+1], -1); + } + + /* Wait for all children to return (holder + all waiters) */ + ret = 0; + for (i = 0; i < CHILD_NR + 1; i++) { + waitpid(pids[i], &wstatus, 0); + if (WEXITSTATUS(wstatus)) + ret = -1; + } + + pthread_barrier_destroy(&barrier); + pthread_barrier_destroy(&barrier2); + + EXPECT_EQ(ret, 0) + TH_LOG("One or more children failed"); +} + +static int child_circular_list(void *arg) +{ + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + static struct lock_struct a, b, c; + struct robust_list_head head; + int ret; + + free(cargs); + + ret = set_list(&head); + ASSERT_EQ(ret, 0) + TH_LOG("set_list error"); + + head.list.next = &a.list; + + /* + * The last element should point to head list, but we short circuit it + */ + a.list.next = &b.list; + b.list.next = &c.list; + c.list.next = &a.list; + + return 0; +} + +/* + * Create a circular robust list. The kernel should be able to destroy the list + * while processing it so it won't be trapped in an infinite loop while handling + * a process exit + */ +TEST(test_circular_list) +{ + int wstatus; + pid_t pid; + + pid = create_child(_metadata, child_circular_list, NULL); + ASSERT_NE(pid, -1); + + wait(&wstatus); + + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); +} + +/* + * Below are tests for the fix of robust release race condition. Please read the following + * thread to learn more about the issue in the first place and why the following functions fix it: + * https://lore.kernel.org/lkml/20260316162316.356674433@kernel.org/ + */ + +/* + * Auxiliary code for binding the vDSO functions + */ +static void *get_vdso_func_addr(const char *function) +{ + const char *vdso_names[] = { + "linux-vdso.so.1", "linux-gate.so.1", "linux-vdso32.so.1", "linux-vdso64.so.1", + }; + + for (int i = 0; i < ARRAY_SIZE(vdso_names); i++) { + void *vdso = dlopen(vdso_names[i], RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + + if (vdso) + return dlsym(vdso, function); + } + return NULL; +} + +/* + * These are the real vDSO function signatures: + * + * __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop) + * __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop) + * + * So for the generic entry point we need to use a void pointer as the last argument + */ +FIXTURE(vdso_unlock) +{ + uint32_t (*vdso)(_Atomic(uint32_t) *lock, uint32_t tid, void *pop); +}; + +FIXTURE_VARIANT(vdso_unlock) +{ + bool is_32; + char func_name[]; +}; + +FIXTURE_SETUP(vdso_unlock) +{ + self->vdso = get_vdso_func_addr(variant->func_name); +} + +FIXTURE_TEARDOWN(vdso_unlock) {} + +FIXTURE_VARIANT_ADD(vdso_unlock, 32) +{ + .func_name = "__vdso_futex_robust_list32_try_unlock", + .is_32 = true, +}; + +FIXTURE_VARIANT_ADD(vdso_unlock, 64) +{ + .func_name = "__vdso_futex_robust_list64_try_unlock", + .is_32 = false, +}; + +/* + * Test the vDSO robust_listXX_try_unlock() for the uncontended case. The virtual syscall should + * return the thread ID of the lock owner, the lock word must be 0 and the list_op_pending should + * be NULL. + */ +TEST_F(vdso_unlock, test_robust_try_unlock_uncontended) +{ + struct lock_struct lock = { .futex = 0 }; + _Atomic(unsigned int) *futex = &lock.futex; + struct robust_list_head head; + uintptr_t exp = (uintptr_t) NULL; + pid_t tid = gettid(); + int ret; + + if (!self->vdso) { + ksft_test_result_skip("%s not found\n", variant->func_name); + return; + } + + *futex = tid; + + ret = set_list(&head); + if (ret) + ksft_test_result_fail("set_robust_list error\n"); + + head.list_op_pending = &lock.list; + + ret = self->vdso(futex, tid, &head.list_op_pending); + + ASSERT_EQ(ret, tid); + ASSERT_EQ(*futex, 0); + + /* Check only the lower 32 bits for the 32-bit entry point */ + if (variant->is_32) { + exp = (uintptr_t)(unsigned long)&lock.list; + exp &= ~0xFFFFFFFFULL; + } + + ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp); +} + +/* + * If the lock is contended, the operation fails. The return value is the value found at the + * futex word (tid | FUTEX_WAITERS), the futex word is not modified and the list_op_pending is_32 + * not cleared. + */ +TEST_F(vdso_unlock, test_robust_try_unlock_contended) +{ + struct lock_struct lock = { .futex = 0 }; + _Atomic(unsigned int) *futex = &lock.futex; + struct robust_list_head head; + pid_t tid = gettid(); + int ret; + + if (!self->vdso) { + ksft_test_result_skip("%s not found\n", variant->func_name); + return; + } + + *futex = tid | FUTEX_WAITERS; + + ret = set_list(&head); + if (ret) + ksft_test_result_fail("set_robust_list error\n"); + + head.list_op_pending = &lock.list; + + ret = self->vdso(futex, tid, &head.list_op_pending); + + ASSERT_EQ(ret, tid | FUTEX_WAITERS); + ASSERT_EQ(*futex, tid | FUTEX_WAITERS); + ASSERT_EQ(head.list_op_pending, &lock.list); +} + +FIXTURE(futex_op) {}; + +FIXTURE_VARIANT(futex_op) +{ + unsigned int op; + unsigned int val3; +}; + +FIXTURE_SETUP(futex_op) {} + +FIXTURE_TEARDOWN(futex_op) {} + +FIXTURE_VARIANT_ADD(futex_op, wake) +{ + .op = FUTEX_WAKE, + .val3 = 0, +}; + +FIXTURE_VARIANT_ADD(futex_op, wake_bitset) +{ + .op = FUTEX_WAKE_BITSET, + .val3 = FUTEX_BITSET_MATCH_ANY, +}; + +FIXTURE_VARIANT_ADD(futex_op, unlock_pi) +{ + .op = FUTEX_UNLOCK_PI, + .val3 = 0, +}; + +FIXTURE_VARIANT_ADD(futex_op, wake32) +{ + .op = FUTEX_WAKE | FUTEX_ROBUST_LIST32, + .val3 = 0, +}; + +FIXTURE_VARIANT_ADD(futex_op, wake_bitset32) +{ + .op = FUTEX_WAKE_BITSET | FUTEX_ROBUST_LIST32, + .val3 = FUTEX_BITSET_MATCH_ANY, +}; + +FIXTURE_VARIANT_ADD(futex_op, unlock_pi32) +{ + .op = FUTEX_UNLOCK_PI | FUTEX_ROBUST_LIST32, + .val3 = 0, +}; + +/* + * The syscall should return the number of tasks waken (for this test, 0), clear the futex word and + * clear list_op_pending + */ +TEST_F(futex_op, test_futex_robust_unlock) +{ + struct lock_struct lock = { .futex = 0 }; + _Atomic(unsigned int) *futex = &lock.futex; + uintptr_t exp = (uintptr_t) NULL; + struct robust_list_head head; + pid_t tid = gettid(); + int ret; + +#ifndef BUILD_64 + if (!(variant->op & FUTEX_ROBUST_LIST32)) { + ksft_test_result_skip("Not supported for 32 bit build\n"); + return; + } +#endif + + *futex = tid | FUTEX_WAITERS; + + ret = set_list(&head); + if (ret) + ksft_test_result_fail("set_robust_list error\n"); + + head.list_op_pending = &lock.list; + + ret = sys_futex_robust_unlock(futex, FUTEX_ROBUST_UNLOCK | variant->op, tid, + &head.list_op_pending, variant->val3); + + ASSERT_EQ(ret, 0); + ASSERT_EQ(*futex, 0); + + if (variant->op & FUTEX_ROBUST_LIST32) { + exp = (uint64_t)(unsigned long)&lock.list; + exp &= ~0xFFFFFFFFULL; + } + + ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/run.sh b/tools/testing/selftests/futex/functional/run.sh new file mode 100755 index 000000000..d1a681b79 --- /dev/null +++ b/tools/testing/selftests/futex/functional/run.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +############################################################################### +# +# Copyright © International Business Machines Corp., 2009 +# +# DESCRIPTION +# Run tests in the current directory. +# +# AUTHOR +# Darren Hart <dvhart@linux.intel.com> +# +# HISTORY +# 2009-Nov-9: Initial version by Darren Hart <dvhart@linux.intel.com> +# 2010-Jan-6: Add futex_wait_uninitialized_heap and futex_wait_private_mapped_file +# by KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> +# +############################################################################### + +echo +./futex_requeue_pi + +echo +./futex_requeue_pi_mismatched_ops + +echo +./futex_requeue_pi_signal_restart + +echo +./futex_wait_timeout + +echo +./futex_wait_wouldblock + +echo +./futex_wait_uninitialized_heap +./futex_wait_private_mapped_file + +echo +./futex_wait + +echo +./futex_requeue + +echo +./futex_waitv + +echo +./futex_priv_hash + +echo +./futex_numa_mpol + +echo +./futex_lock_pi_exiting diff --git a/tools/testing/selftests/futex/include/atomic.h b/tools/testing/selftests/futex/include/atomic.h new file mode 100644 index 000000000..428bcd921 --- /dev/null +++ b/tools/testing/selftests/futex/include/atomic.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/****************************************************************************** + * + * Copyright © International Business Machines Corp., 2009 + * + * DESCRIPTION + * GCC atomic builtin wrappers + * http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html + * + * AUTHOR + * Darren Hart <dvhart@linux.intel.com> + * + * HISTORY + * 2009-Nov-17: Initial version by Darren Hart <dvhart@linux.intel.com> + * + *****************************************************************************/ + +#ifndef _ATOMIC_H +#define _ATOMIC_H + +typedef struct { + volatile int val; +} atomic_t; + +#define ATOMIC_INITIALIZER { 0 } + +/** + * atomic_cmpxchg() - Atomic compare and exchange + * @uaddr: The address of the futex to be modified + * @oldval: The expected value of the futex + * @newval: The new value to try and assign the futex + * + * Return the old value of addr->val. + */ +static inline int +atomic_cmpxchg(atomic_t *addr, int oldval, int newval) +{ + return __sync_val_compare_and_swap(&addr->val, oldval, newval); +} + +/** + * atomic_inc() - Atomic incrememnt + * @addr: Address of the variable to increment + * + * Return the new value of addr->val. + */ +static inline int +atomic_inc(atomic_t *addr) +{ + return __sync_add_and_fetch(&addr->val, 1); +} + +/** + * atomic_dec() - Atomic decrement + * @addr: Address of the variable to decrement + * + * Return the new value of addr-val. + */ +static inline int +atomic_dec(atomic_t *addr) +{ + return __sync_sub_and_fetch(&addr->val, 1); +} + +/** + * atomic_set() - Atomic set + * @addr: Address of the variable to set + * @newval: New value for the atomic_t + * + * Return the new value of addr->val. + */ +static inline int +atomic_set(atomic_t *addr, int newval) +{ + addr->val = newval; + return newval; +} + +#endif diff --git a/tools/testing/selftests/futex/include/futex2test.h b/tools/testing/selftests/futex/include/futex2test.h new file mode 100644 index 000000000..53e88b60a --- /dev/null +++ b/tools/testing/selftests/futex/include/futex2test.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Futex2 library addons for futex tests + * + * Copyright 2021 Collabora Ltd. + */ +#include <linux/time_types.h> +#include <errno.h> +#include <stdint.h> +#include <stdbool.h> + +#define u64_to_ptr(x) ((void *)(uintptr_t)(x)) + +#ifndef __NR_futex_waitv +#define __NR_futex_waitv 449 +struct futex_waitv { + __u64 val; + __u64 uaddr; + __u32 flags; + __u32 __reserved; +}; +#endif + +#ifndef __NR_futex_wake +#define __NR_futex_wake 454 +#endif + +#ifndef __NR_futex_wait +#define __NR_futex_wait 455 +#endif + +#ifndef FUTEX2_SIZE_U32 +#define FUTEX2_SIZE_U32 0x02 +#endif + +#ifndef FUTEX2_NUMA +#define FUTEX2_NUMA 0x04 +#endif + +#ifndef FUTEX2_MPOL +#define FUTEX2_MPOL 0x08 +#endif + +#ifndef FUTEX2_PRIVATE +#define FUTEX2_PRIVATE FUTEX_PRIVATE_FLAG +#endif + +#ifndef FUTEX2_NO_NODE +#define FUTEX_NO_NODE (-1) +#endif + +#ifndef FUTEX_32 +#define FUTEX_32 FUTEX2_SIZE_U32 +#endif + +struct futex32_numa { + futex_t futex; + futex_t numa; +}; + +/** + * futex_waitv - Wait at multiple futexes, wake on any + * @waiters: Array of waiters + * @nr_waiters: Length of waiters array + * @flags: Operation flags + * @timo: Optional timeout for operation + */ +static inline int futex_waitv(volatile struct futex_waitv *waiters, unsigned long nr_waiters, + unsigned long flags, struct timespec *timo, clockid_t clockid) +{ + struct __kernel_timespec ts = { + .tv_sec = timo->tv_sec, + .tv_nsec = timo->tv_nsec, + }; + + return syscall(__NR_futex_waitv, waiters, nr_waiters, flags, &ts, clockid); +} + +/* + * futex_wait() - block on uaddr with optional timeout + * @val: Expected value + * @flags: FUTEX2 flags + * @timeout: Relative timeout + * @clockid: Clock id for the timeout + */ +static inline int futex2_wait(void *uaddr, long val, unsigned int flags, + struct timespec *timeout, clockid_t clockid) +{ + return syscall(__NR_futex_wait, uaddr, val, ~0U, flags, timeout, clockid); +} + +/* + * futex2_wake() - Wake a number of futexes + * @nr: Number of threads to wake at most + * @flags: FUTEX2 flags + */ +static inline int futex2_wake(void *uaddr, int nr, unsigned int flags) +{ + return syscall(__NR_futex_wake, uaddr, ~0U, nr, flags); +} + +static inline bool is_futex_waitv_supported(void) +{ + struct timespec ts = {0, 0}; + int res = futex_waitv(NULL, 0, 0, &ts, CLOCK_MONOTONIC); + + return !(res < 0 && errno == ENOSYS); +} diff --git a/tools/testing/selftests/futex/include/futex_thread.h b/tools/testing/selftests/futex/include/futex_thread.h new file mode 100644 index 000000000..a90882960 --- /dev/null +++ b/tools/testing/selftests/futex/include/futex_thread.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _FUTEX_THREAD_H +#define _FUTEX_THREAD_H +#include <errno.h> +#include <pthread.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "kselftest_harness.h" + +#define USEC_PER_SEC 1000000L +#define WAIT_FOR_THREAD_SECS 1 +#define WAIT_FOR_THREAD_USECS (WAIT_FOR_THREAD_SECS * USEC_PER_SEC) +#define WAIT_THREAD_RETRIES 100 + +struct futex_thread { + pthread_t thread; + pthread_barrier_t barrier; + pid_t tid; + int (*threadfn)(void *arg); + void *arg; + int retval; +}; + +static inline int __wait_for_thread(FILE *fp, struct __test_metadata *_metadata) +{ + unsigned int sleep_time_us = WAIT_FOR_THREAD_USECS / WAIT_THREAD_RETRIES; + char buf[80] = ""; + + for (int i = 0; i < WAIT_THREAD_RETRIES; i++) { + if (!fgets(buf, sizeof(buf), fp)) + return EIO; + if (!strncmp(buf, "futex", 5)) + return 0; + usleep(sleep_time_us); + rewind(fp); + } + + TH_LOG("/proc/$PID/wchan contains \"%s\". Trying to continue.", buf); + return 0; +} + +static void *__futex_thread_fn(void *arg) +{ + struct futex_thread *t = arg; + + t->tid = gettid(); + pthread_barrier_wait(&t->barrier); + t->retval = t->threadfn(t->arg); + return NULL; +} + +/** + * futex_wait_for_thread - Wait for the child thread to sleep in the futex context + * @t: Thread handle. + * @_metadata: Test metadata for TH_LOG() context + */ +static inline int futex_wait_for_thread(struct futex_thread *t, struct __test_metadata *_metadata) +{ + char fname[80]; + FILE *fp; + int res; + + snprintf(fname, sizeof(fname), "/proc/%d/wchan", t->tid); + fp = fopen(fname, "r"); + if (!fp) { + /* If /proc/... is not available, sleep */ + if (errno != ENOENT) + return errno; + TH_LOG("/proc/$PID/wchan not accessible, continue with sleep()"); + sleep(WAIT_FOR_THREAD_SECS); + return 0; + } + + res = __wait_for_thread(fp, _metadata); + fclose(fp); + return res; +} + +/** + * futex_thread_create - Create a new thread for testing. + * @t: The handle of the newly created thread. + * @threadfn: The new thread starts execution by invoking threadfn + * @arg: The parameters passed to threadfn. + */ +static inline int futex_thread_create(struct futex_thread *t, int (*threadfn)(void *), void *arg) +{ + pthread_barrier_init(&t->barrier, NULL, 2); + + t->tid = 0; + t->threadfn = threadfn; + t->arg = arg; + + if (pthread_create(&t->thread, NULL, __futex_thread_fn, t) < 0) { + int ret = errno; + pthread_barrier_destroy(&t->barrier); + return ret; + } + + pthread_barrier_wait(&t->barrier); + return 0; +} + +/** + * futex_thread_destroy - Wait for and reclaim the resources of the thread. + * @t: Thread handle. + */ +static inline int futex_thread_destroy(struct futex_thread *t) +{ + pthread_join(t->thread, NULL); + pthread_barrier_destroy(&t->barrier); + return t->retval; +} + +#endif diff --git a/tools/testing/selftests/futex/include/futextest.h b/tools/testing/selftests/futex/include/futextest.h new file mode 100644 index 000000000..df33f31d6 --- /dev/null +++ b/tools/testing/selftests/futex/include/futextest.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/****************************************************************************** + * + * Copyright © International Business Machines Corp., 2009 + * + * DESCRIPTION + * Glibc independent futex library for testing kernel functionality. + * + * AUTHOR + * Darren Hart <dvhart@linux.intel.com> + * + * HISTORY + * 2009-Nov-6: Initial version by Darren Hart <dvhart@linux.intel.com> + * + *****************************************************************************/ + +#ifndef _FUTEXTEST_H +#define _FUTEXTEST_H + +#include <unistd.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <linux/futex.h> + +typedef volatile u_int32_t futex_t; +#define FUTEX_INITIALIZER 0 + +/* Define the newer op codes if the system header file is not up to date. */ +#ifndef FUTEX_WAIT_BITSET +#define FUTEX_WAIT_BITSET 9 +#endif +#ifndef FUTEX_WAKE_BITSET +#define FUTEX_WAKE_BITSET 10 +#endif +#ifndef FUTEX_WAIT_REQUEUE_PI +#define FUTEX_WAIT_REQUEUE_PI 11 +#endif +#ifndef FUTEX_CMP_REQUEUE_PI +#define FUTEX_CMP_REQUEUE_PI 12 +#endif +#ifndef FUTEX_ROBUST_UNLOCK +#define FUTEX_ROBUST_UNLOCK 512 +#endif +#ifndef FUTEX_ROBUST_LIST32 +#define FUTEX_ROBUST_LIST32 1024 +#endif +#ifndef FUTEX_WAIT_REQUEUE_PI_PRIVATE +#define FUTEX_WAIT_REQUEUE_PI_PRIVATE (FUTEX_WAIT_REQUEUE_PI | \ + FUTEX_PRIVATE_FLAG) +#endif +#ifndef FUTEX_REQUEUE_PI_PRIVATE +#define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | \ + FUTEX_PRIVATE_FLAG) +#endif + +/* + * SYS_futex is expected from system C library, in glibc some 32-bit + * architectures (e.g. RV32) are using 64-bit time_t, therefore it doesn't have + * SYS_futex defined but just SYS_futex_time64. Define SYS_futex as + * SYS_futex_time64 in this situation to ensure the compilation and the + * compatibility. + */ +#if !defined(SYS_futex) && defined(SYS_futex_time64) +#define SYS_futex SYS_futex_time64 +#endif + +/* + * On 32bit systems if we use "-D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64" or if + * we are using a newer compiler then the size of the timestamps will be 64bit, + * however, the SYS_futex will still point to the 32bit futex system call. + */ +#if __SIZEOF_POINTER__ == 4 && defined(SYS_futex_time64) && \ + defined(_TIME_BITS) && _TIME_BITS == 64 +# undef SYS_futex +# define SYS_futex SYS_futex_time64 +#endif + +/** + * futex() - SYS_futex syscall wrapper + * @uaddr: address of first futex + * @op: futex op code + * @val: typically expected value of uaddr, but varies by op + * @timeout: typically an absolute struct timespec (except where noted + * otherwise). Overloaded by some ops + * @uaddr2: address of second futex for some ops\ + * @val3: varies by op + * @opflags: flags to be bitwise OR'd with op, such as FUTEX_PRIVATE_FLAG + * + * futex() is used by all the following futex op wrappers. It can also be + * used for misuse and abuse testing. Generally, the specific op wrappers + * should be used instead. It is a macro instead of an static inline function as + * some of the types over overloaded (timeout is used for nr_requeue for + * example). + * + * These argument descriptions are the defaults for all + * like-named arguments in the following wrappers except where noted below. + */ +#define futex(uaddr, op, val, timeout, uaddr2, val3, opflags) \ + syscall(SYS_futex, uaddr, op | opflags, val, timeout, uaddr2, val3) + +/** + * futex_wait() - block on uaddr with optional timeout + * @timeout: relative timeout + */ +static inline int +futex_wait(futex_t *uaddr, futex_t val, struct timespec *timeout, int opflags) +{ + return futex(uaddr, FUTEX_WAIT, val, timeout, NULL, 0, opflags); +} + +/** + * futex_wake() - wake one or more tasks blocked on uaddr + * @nr_wake: wake up to this many tasks + */ +static inline int +futex_wake(futex_t *uaddr, int nr_wake, int opflags) +{ + return futex(uaddr, FUTEX_WAKE, nr_wake, NULL, NULL, 0, opflags); +} + +/** + * futex_wait_bitset() - block on uaddr with bitset + * @bitset: bitset to be used with futex_wake_bitset + */ +static inline int +futex_wait_bitset(futex_t *uaddr, futex_t val, struct timespec *timeout, + u_int32_t bitset, int opflags) +{ + return futex(uaddr, FUTEX_WAIT_BITSET, val, timeout, NULL, bitset, + opflags); +} + +/** + * futex_wake_bitset() - wake one or more tasks blocked on uaddr with bitset + * @bitset: bitset to compare with that used in futex_wait_bitset + */ +static inline int +futex_wake_bitset(futex_t *uaddr, int nr_wake, u_int32_t bitset, int opflags) +{ + return futex(uaddr, FUTEX_WAKE_BITSET, nr_wake, NULL, NULL, bitset, + opflags); +} + +/** + * futex_lock_pi() - block on uaddr as a PI mutex + * @detect: whether (1) or not (0) to perform deadlock detection + */ +static inline int +futex_lock_pi(futex_t *uaddr, struct timespec *timeout, int detect, + int opflags) +{ + return futex(uaddr, FUTEX_LOCK_PI, detect, timeout, NULL, 0, opflags); +} + +/** + * futex_unlock_pi() - release uaddr as a PI mutex, waking the top waiter + */ +static inline int +futex_unlock_pi(futex_t *uaddr, int opflags) +{ + return futex(uaddr, FUTEX_UNLOCK_PI, 0, NULL, NULL, 0, opflags); +} + +/** + * futex_wake_op() - FIXME: COME UP WITH A GOOD ONE LINE DESCRIPTION + */ +static inline int +futex_wake_op(futex_t *uaddr, futex_t *uaddr2, int nr_wake, int nr_wake2, + int wake_op, int opflags) +{ + return futex(uaddr, FUTEX_WAKE_OP, nr_wake, nr_wake2, uaddr2, wake_op, + opflags); +} + +/** + * futex_requeue() - requeue without expected value comparison, deprecated + * @nr_wake: wake up to this many tasks + * @nr_requeue: requeue up to this many tasks + * + * Due to its inherently racy implementation, futex_requeue() is deprecated in + * favor of futex_cmp_requeue(). + */ +static inline int +futex_requeue(futex_t *uaddr, futex_t *uaddr2, int nr_wake, int nr_requeue, + int opflags) +{ + return futex(uaddr, FUTEX_REQUEUE, nr_wake, nr_requeue, uaddr2, 0, + opflags); +} + +/** + * futex_cmp_requeue() - requeue tasks from uaddr to uaddr2 + * @nr_wake: wake up to this many tasks + * @nr_requeue: requeue up to this many tasks + */ +static inline int +futex_cmp_requeue(futex_t *uaddr, futex_t val, futex_t *uaddr2, int nr_wake, + int nr_requeue, int opflags) +{ + return futex(uaddr, FUTEX_CMP_REQUEUE, nr_wake, nr_requeue, uaddr2, + val, opflags); +} + +/** + * futex_wait_requeue_pi() - block on uaddr and prepare to requeue to uaddr2 + * @uaddr: non-PI futex source + * @uaddr2: PI futex target + * + * This is the first half of the requeue_pi mechanism. It shall always be + * paired with futex_cmp_requeue_pi(). + */ +static inline int +futex_wait_requeue_pi(futex_t *uaddr, futex_t val, futex_t *uaddr2, + struct timespec *timeout, int opflags) +{ + return futex(uaddr, FUTEX_WAIT_REQUEUE_PI, val, timeout, uaddr2, 0, + opflags); +} + +/** + * futex_cmp_requeue_pi() - requeue tasks from uaddr to uaddr2 (PI aware) + * @uaddr: non-PI futex source + * @uaddr2: PI futex target + * @nr_wake: wake up to this many tasks + * @nr_requeue: requeue up to this many tasks + */ +static inline int +futex_cmp_requeue_pi(futex_t *uaddr, futex_t val, futex_t *uaddr2, int nr_wake, + int nr_requeue, int opflags) +{ + return futex(uaddr, FUTEX_CMP_REQUEUE_PI, nr_wake, nr_requeue, uaddr2, + val, opflags); +} + +/** + * futex_cmpxchg() - atomic compare and exchange + * @uaddr: The address of the futex to be modified + * @oldval: The expected value of the futex + * @newval: The new value to try and assign the futex + * + * Implement cmpxchg using gcc atomic builtins. + * http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html + * + * Return the old futex value. + */ +static inline u_int32_t +futex_cmpxchg(futex_t *uaddr, u_int32_t oldval, u_int32_t newval) +{ + return __sync_val_compare_and_swap(uaddr, oldval, newval); +} + +/** + * futex_dec() - atomic decrement of the futex value + * @uaddr: The address of the futex to be modified + * + * Return the new futex value. + */ +static inline u_int32_t +futex_dec(futex_t *uaddr) +{ + return __sync_sub_and_fetch(uaddr, 1); +} + +/** + * futex_inc() - atomic increment of the futex value + * @uaddr: the address of the futex to be modified + * + * Return the new futex value. + */ +static inline u_int32_t +futex_inc(futex_t *uaddr) +{ + return __sync_add_and_fetch(uaddr, 1); +} + +/** + * futex_set() - atomic decrement of the futex value + * @uaddr: the address of the futex to be modified + * @newval: New value for the atomic_t + * + * Return the new futex value. + */ +static inline u_int32_t +futex_set(futex_t *uaddr, u_int32_t newval) +{ + *uaddr = newval; + return newval; +} + +#endif diff --git a/tools/testing/selftests/futex/run.sh b/tools/testing/selftests/futex/run.sh new file mode 100755 index 000000000..5e76ea18f --- /dev/null +++ b/tools/testing/selftests/futex/run.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +############################################################################### +# +# Copyright © International Business Machines Corp., 2009 +# +# DESCRIPTION +# Run all tests under the functional, performance, and stress directories. +# Format and summarize the results. +# +# AUTHOR +# Darren Hart <dvhart@linux.intel.com> +# +# HISTORY +# 2009-Nov-9: Initial version by Darren Hart <dvhart@linux.intel.com> +# +############################################################################### + +# Test for a color capable shell and pass the result to the subdir scripts +USE_COLOR=0 +tput setf 7 || tput setaf 7 +if [ $? -eq 0 ]; then + USE_COLOR=1 + tput sgr0 +fi +export USE_COLOR + +(cd functional; ./run.sh) |
