diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-09-13 12:27:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-09-13 12:27:00 -0700 |
| commit | 22098763a10d9c1340827fcf6edab66f153b27f0 (patch) | |
| tree | 3962f95a34df034652ff93686cdd156c7197f2d9 /tools/testing/selftests/futex | |
| download | linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.tar.gz linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.zip | |
Merge tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Don't destroy user event fields when removal fails
User event fields are destroyed before the event is removed from
visibility. But that can fail leaving the still visible event with no
fields. Move the destroying of the fields to after the event is
successfully removed from visibility.
- Initialize function graph state is fork before calling
copy_exec_state()
For non-CLONE_VM forks, copy_exec_state() allocates a new
task_exec_state. If that allocation fails, ftrace_graph_exit_task()
will free the tasks ret_stack pointer. Since that pointer is still
using the parent's ret_stack, it mistakenly frees the parent's
pointer too.
Call ftrace_graph_init() on the task first which will NULL out the
new tasks's ret_stack and if the copy fails, it will not free
anything.
- Remove FGRAPH_MAX_INDEX
The macro FGRAPH_MAX_INDEX was added but never used. Remove it.
- Save ent_size in function graph printing of nested functions
The function graph tracer needs to look at the next event to see if
the next event is the return of the current function entry. If it is,
it prints a single line:
ktime_get();
Otherwise it prints it like a nested function:
tick_nohz_irq_exit() {
ktime_get();
kcpustat_irq_exit();
}
In order to look at the next event, it must save the current event so
that it has the information to print from it. It saves the event in
the iterator descriptor called "ent". What it doesn't save is the
ent_size of the event which is now used to know if the function graph
arguments are to be printed. The peek doesn't save the size so the
size used happens to be that of the size of the last event that was
seen.
Save the entry event size in the iterator descriptor so that the
correct size is used.
- Fix several errors with freeing data in the histogram code
The histogram code had a lot of leaked or or incorrect accounting
when failures happen. Correct them.
- Fix histogram regression of .percent and .graph modifiers
Up until 6.3 histogram values could have "percent" or "graph"
modifiers that changed how they were printed. But a change that added
restricting histograms values from being strings, stack traces and
other modifiers inadvertently prevented them from using the percent
and graph modifiers, which were legal use cases for values.
Put back the percent and graph modifiers.
- Fix various typos in the comments
- Set the trace_clock before initializing a histogram with clock
argument
The histogram API allows the user to specific which trace clock to
use via a "clock=" string. The histogram is set up first before the
clock is checked. If the passed in clock is not valid, it exits
without fully fixing up the histogram leaving it on the list and a
use-after-free can trigger.
Update the clock argument first and if it fails then exit gracefully
before the histogram trigger is placed on any lists.
- Restore :mod: trailer after parsing in ftrace_set_clr_event
The function ftrace_set_clr_event() modifies the parse string and
needs to put it back to what was passed in. It searches for ":mod:"
via a strsep() but fails to put back the first ':' in the string.
Add back the ':' in the passed in string.
- Take trace_array reference when opening a tracer options file
The options files are dynamically created and some tracers add their
own options. When a tracer adds their own list of options, the
trace_array holding them has an array to hold the list of options for
each tracer. This array increases in size via a krealloc(), and the
new entry gets a newly allocated array to hold the options of the new
tracer being added.
The element in each entry of the tracer's option array holds a
pointer back to the trace_array, a pointer to the tracer it is
associated to, a pointer to the flags of the option.
The issue is that these arrays are freed when the trace_array is
freed when its instance it represents is removed from the instances
directory. There's a race that an open of one of these options files
can happen when the instance is being removed.
Add a new helper function to be called by the open function of the
options file to iterate all existing trace_arrays under a lock and
find the one that has the given option element in one of it's tracer
arrays. If found, then update the associated trace_array's reference
counter to keep it from being freed. If not found, have the open call
return -ENODEV.
- Disable interrupts when acquiring the lock in rb_wake_up_waiters()
The function rb_wake_up_waiters() assumes it will be called in
interrupt context and does not disable irqs when taking
cpu_buffer->reader_lock, which can be called in hard interrupt
context. The issue is in PREEMPT_RT, this function is called in
thread context leaving this lock open to a deadlock.
Take the lock with interrupts disabled.
- Use rcu_assign_pointer() for tmp_ops filter hash
The tmp_ops used in update_ftrace_direct_mod() assigns its
filter_hash field directly, but that field is annotated as __rcu and
sparse complains. Assign it with rcu_assign_pointer()
- Fix use-after-free in enable_trigger_private_data_free()
The trace_event_call is accessed through the event_trigger_data's
trace_event_file pointer to put the trace_event_call on freeing. The
issue is that the trace_event_file data may have been freed already
causing a use-after-free. Add a field to the event_trigger_data that
points directly to the trace_event_call so that it can decrement its
reference directly without needing to go through the
trace_event_file.
- Fix accounting of buffer data remote headers
trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount
the number of pages is needed for the asked for size as it doesn't
take into account the meta data on each page. Add a helper function
to do the calculation properly and use that in these functions.
- Catch nr_page_va overflow in ring_buffer_desc sizing
The number of pages per remote ring buffer is capped by
ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to
overflow that field would silently allocate a descriptor smaller than
what was asked for.
- Do not resize the subbuf order if any per_cpu buffer is disabled
The mmapping of ring buffers disables resizing the subbuffers, but it
is done per-cpu whereas the subbuf size change is done for all the
per_cpu buffers under the buffer->mutex. It could change the size of
some while the mapping is happening on others. Have the resize of the
subbuf order check all the per_cpu buffers under the lock to see if
any of them is disabled before starting and causing an inconsistency
between buffers that are being mapped.
* tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (25 commits)
ring-buffer: Check resize_disabled before publishing the new subbuf order
tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing
tracing/remotes: Account for ring buffer page header in size calculation
tracing: Don't dereference trace_event_file in deferred trigger free
ftrace: Use rcu_assign_pointer() for tmp_ops filter hash
ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters()
tracing: Take trace_array reference when opening a tracer options file
tracing: Fix ring_buffer_read_page_size() kernel-doc
tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event()
tracing: Fix memory corruption from a "STACKTRACE" histogram key
tracing: Fix memory corruption from the histogram stacktrace modifier
tracing: Undo the registration when enabling the histogram trigger fails
tracing: Take the reference before publishing the named histogram trigger
tracing: Set the trace clock before registering the histogram trigger
tracing: Fix typo "preceeded" in comment
tracing: Fix typo "availabe" in comment
tracing: Let histogram values keep the percent and graph modifiers
tracing: Keep the entry count when the histogram stats allocation fails
tracing: Free histogram the field rejected for a bad modifier
tracing: Free histogram the var ref when its initialization fails
...
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) |
