mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: Peter Zijlstra <peterz@infradead.org>, Ingo Molnar <mingo@redhat.com>
Cc: linux-kernel@vger.kernel.org,
	Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
	Juri Lelli <juri.lelli@redhat.com>,
	Vincent Guittot <vincent.guittot@linaro.org>,
	Dietmar Eggemann <dietmar.eggemann@arm.com>,
	Steven Rostedt <rostedt@goodmis.org>,
	Ben Segall <bsegall@google.com>, Mel Gorman <mgorman@suse.de>,
	Valentin Schneider <vschneid@redhat.com>,
	K Prateek Nayak <kprateek.nayak@amd.com>,
	"Paul E. McKenney" <paulmck@kernel.org>,
	Michal Clapinski <mclapinski@google.com>,
	Andrei Vagin <avagin@gmail.com>, Shuah Khan <shuah@kernel.org>,
	Aniket Gattani <aniketgattani@google.com>,
	linux-kselftest@vger.kernel.org,
	Andrew Morton <akpm@linux-foundation.org>,
	Chris Gellermann <christian.gellermann@codasip.com>,
	Bala-Vignesh-Reddy <reddybalavignesh9979@gmail.com>,
	Wei Yang <richard.weiyang@gmail.com>,
	Thomas Gleixner <tglx@kernel.org>,
	Huacai Chen <chenhuacai@kernel.org>,
	WANG Xuerui <kernel@xen0n.name>,
	loongarch@lists.linux.dev,
	"James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>,
	Helge Deller <deller@gmx.de>,
	John David Anglin <dave.anglin@bell.net>,
	linux-parisc@vger.kernel.org, Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	Paul Walmsley <pjw@kernel.org>,
	Palmer Dabbelt <palmer@dabbelt.com>,
	Albert Ou <aou@eecs.berkeley.edu>,
	Alexandre Ghiti <alex@ghiti.fr>, Guo Ren <guoren@kernel.org>,
	linux-doc@vger.kernel.org, linux-riscv@lists.infradead.org,
	linux-csky@vger.kernel.org, Jonas Bonn <jonas@southpole.se>,
	Stefan Kristiansson <stefan.kristiansson@saunalahti.fi>,
	Stafford Horne <shorne@gmail.com>,
	linux-openrisc@vger.kernel.org,
	Yoshinori Sato <ysato@users.sourceforge.jp>,
	Rich Felker <dalias@libc.org>,
	John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>,
	linux-sh@vger.kernel.org, Chris Zankel <chris@zankel.net>,
	Max Filippov <jcmvbkbc@gmail.com>,
	linux-arch@vger.kernel.org, Borislav Petkov <bp@alien8.de>,
	Dave Hansen <dave.hansen@linux.intel.com>,
	x86@kernel.org, "H. Peter Anvin" <hpa@zytor.com>,
	Catalin Marinas <catalin.marinas@arm.com>,
	Will Deacon <will@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	linux-arm-kernel@lists.infradead.org,
	Andy Lutomirski <luto@kernel.org>,
	Alan Stern <stern@rowland.harvard.edu>,
	Andrea Parri <parri.andrea@gmail.com>,
	Boqun Feng <boqun@kernel.org>,
	Nicholas Piggin <npiggin@gmail.com>,
	David Howells <dhowells@redhat.com>,
	Jade Alglave <j.alglave@ucl.ac.uk>,
	Luc Maranget <luc.maranget@inria.fr>,
	Akira Yokosawa <akiyks@gmail.com>,
	Daniel Lustig <dlustig@nvidia.com>,
	Joel Fernandes <joelagnelf@nvidia.com>,
	lkmm@lists.linux.dev, bpf@vger.kernel.org,
	Naveen N Rao <naveen@kernel.org>,
	"David S. Miller" <davem@davemloft.net>,
	Masami Hiramatsu <mhiramat@kernel.org>,
	linux-trace-kernel@vger.kernel.org
Subject: [RFC PATCH 06/16] selftests/membarrier: Test which CPUs the expedited commands interrupt
Date: Fri, 13 Nov 2026 08:18:53 -0500	[thread overview]
Message-ID: <20261113131903.157186-7-mathieu.desnoyers@efficios.com> (raw)
In-Reply-To: <20261113131903.157186-1-mathieu.desnoyers@efficios.com>

The existing tests only check the return value of each command. They
cannot tell whether a command reached the threads it must reach.

Add membarrier_test_ipi. A test cannot observe a missing memory
barrier reliably. It can observe a missing IPI in /proc/interrupts.
The comment at the top of the file describes the method. The test
cases are:

 - global expedited reaches a registered process and skips an
   unregistered one,
 - global expedited reaches a registered process after one of its
   threads exits,
 - global expedited reaches a process which registered on another CPU
   and came back to a CPU which stayed idle meanwhile,
 - global expedited reaches each thread of a process which registered
   while multi-threaded,
 - global expedited reaches a forked child and skips a process after
   exec,
 - private expedited, sync core and rseq reach each thread,
 - rseq with MEMBARRIER_CMD_FLAG_CPU reaches only the CPU it names,
 - private expedited skips another registered process,
 - private expedited reaches a CLONE_VM task of another thread group.

Each test case runs in its own process, because a membarrier
registration cannot be undone.

The thread exit and idle CPU cases fail without commit
"sched/membarrier: Fix stale runqueue state after a lazy mm switch".

The test is skipped with fewer than 3 allowed CPUs. It is also skipped
when /proc/interrupts has no function call interrupt count.

Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
---
 tools/testing/selftests/membarrier/.gitignore |   1 +
 tools/testing/selftests/membarrier/Makefile   |   1 +
 .../membarrier/membarrier_test_ipi.c          | 739 ++++++++++++++++++
 3 files changed, 741 insertions(+)
 create mode 100644 tools/testing/selftests/membarrier/membarrier_test_ipi.c

diff --git a/tools/testing/selftests/membarrier/.gitignore b/tools/testing/selftests/membarrier/.gitignore
index 031920478205..58969a5e4613 100644
--- a/tools/testing/selftests/membarrier/.gitignore
+++ b/tools/testing/selftests/membarrier/.gitignore
@@ -2,3 +2,4 @@
 membarrier_test_multi_thread
 membarrier_test_single_thread
 membarrier_rseq_stress
+membarrier_test_ipi
diff --git a/tools/testing/selftests/membarrier/Makefile b/tools/testing/selftests/membarrier/Makefile
index 20bb66380090..ee5b9deb808b 100644
--- a/tools/testing/selftests/membarrier/Makefile
+++ b/tools/testing/selftests/membarrier/Makefile
@@ -4,6 +4,7 @@ LDLIBS += -lpthread
 
 TEST_GEN_PROGS := membarrier_test_single_thread \
 		membarrier_test_multi_thread \
+		membarrier_test_ipi \
 		membarrier_rseq_stress
 
 LOCAL_HDRS += membarrier_test_impl.h
diff --git a/tools/testing/selftests/membarrier/membarrier_test_ipi.c b/tools/testing/selftests/membarrier/membarrier_test_ipi.c
new file mode 100644
index 000000000000..49b74844c1f4
--- /dev/null
+++ b/tools/testing/selftests/membarrier/membarrier_test_ipi.c
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Check which CPUs the expedited membarrier commands interrupt.
+ *
+ * An expedited command sends an IPI to each CPU which runs a targeted
+ * thread. The IPI is the memory barrier. A test cannot observe a
+ * missing memory barrier reliably. It can observe a missing IPI: the
+ * kernel counts the function call interrupts of each CPU in
+ * /proc/interrupts.
+ *
+ * Each test case pins a "target" which spins in user-space on its own
+ * CPU. A "sender" pinned on another CPU issues the command NR_CMDS
+ * times. The test then compares the count of function call interrupts
+ * of the target CPU before and after.
+ *
+ * Each test case runs in its own process because a membarrier
+ * registration cannot be undone.
+ */
+#define _GNU_SOURCE
+#include <linux/membarrier.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <syscall.h>
+#include <stdatomic.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sched.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+
+#include "kselftest.h"
+
+#define NR_CMDS		1000
+/*
+ * A target which must be interrupted can lose a few IPIs when another
+ * task preempts it. A target which must not be interrupted can receive
+ * a few unrelated IPIs. The observed counts are either close to
+ * NR_CMDS or close to zero. Keep a wide margin on both sides.
+ */
+#define MIN_HIT		(NR_CMDS / 2)
+#define MAX_MISS	(NR_CMDS / 4)
+
+#define MAX_TARGETS	2
+/* Seconds to wait for the targets of a test case. */
+#define READY_TIMEOUT	10
+#define STACK_SIZE	(64 * 1024)
+
+/*
+ * Exit status of a test case process. Each skip status has its own
+ * reason, which run_case() reports. CASE_FAIL is a failure of
+ * membarrier. CASE_ERROR is a failure of the test setup.
+ */
+enum {
+	CASE_PASS = 0,
+	CASE_FAIL = 1,
+	CASE_SKIP_NO_COUNT = 2,
+	CASE_SKIP_NO_CMD = 3,
+	CASE_ERROR = 4,
+};
+
+struct shared {
+	atomic_int ready;
+	atomic_int stop;
+};
+
+static struct shared *shared;
+static int shared_fd;
+
+/* CPU of the sender and CPUs of the targets. */
+static int sender_cpu, target_cpu[MAX_TARGETS];
+
+static int sys_membarrier(int cmd, unsigned int flags, int cpu_id)
+{
+	return syscall(__NR_membarrier, cmd, flags, cpu_id);
+}
+
+/* Pin the task @pid on @cpu. A @pid of 0 is the calling thread. */
+static void pin_task(pid_t pid, int cpu)
+{
+	cpu_set_t set;
+
+	CPU_ZERO(&set);
+	CPU_SET(cpu, &set);
+	if (sched_setaffinity(pid, sizeof(set), &set)) {
+		perror("sched_setaffinity");
+		_exit(CASE_ERROR);
+	}
+}
+
+static void pin_to_cpu(int cpu)
+{
+	pin_task(0, cpu);
+}
+
+/* Die with the parent. A killed test must leave no spinning target. */
+static void die_with_parent(void)
+{
+	if (prctl(PR_SET_PDEATHSIG, SIGKILL)) {
+		perror("prctl");
+		_exit(CASE_ERROR);
+	}
+}
+
+static void must_join(pthread_t thread)
+{
+	errno = pthread_join(thread, NULL);
+	if (errno) {
+		perror("pthread_join");
+		_exit(CASE_ERROR);
+	}
+}
+
+/* Wait for the child @pid. Its setup failed unless it exits with 0. */
+static void must_wait(pid_t pid)
+{
+	int status;
+
+	if (waitpid(pid, &status, 0) != pid) {
+		perror("waitpid");
+		_exit(CASE_ERROR);
+	}
+	if (!WIFEXITED(status) || WEXITSTATUS(status)) {
+		fprintf(stderr, "# child %d did not exit with 0\n", pid);
+		_exit(CASE_ERROR);
+	}
+}
+
+static void must_register(int cmd)
+{
+	if (sys_membarrier(cmd, 0, 0)) {
+		perror("membarrier registration");
+		_exit(CASE_ERROR);
+	}
+}
+
+/* Announce that this target is in place, then spin in user-space. */
+static void spin(void)
+{
+	die_with_parent();
+	atomic_fetch_add(&shared->ready, 1);
+	while (!atomic_load(&shared->stop))
+		;
+}
+
+/*
+ * Wait until @nr targets spin. Report a setup error when they do not
+ * show up: a target may have died in its setup.
+ */
+static void wait_ready(int nr)
+{
+	time_t start = time(NULL);
+
+	while (atomic_load(&shared->ready) < nr) {
+		if (time(NULL) - start > READY_TIMEOUT) {
+			fprintf(stderr, "# the targets are not ready\n");
+			atomic_store(&shared->stop, 1);
+			_exit(CASE_ERROR);
+		}
+		sched_yield();
+	}
+}
+
+/*
+ * Read the count of function call interrupts of each CPU of @cpus.
+ * Return false when /proc/interrupts has no such count for one of
+ * them. An offline CPU has no count.
+ */
+static bool read_ipi_counts(const int *cpus, int nr_cpus, long *counts)
+{
+	int *col_cpu = NULL, nr_cols = 0, i, c;
+	char *line = NULL, *p, *end;
+	unsigned int seen = 0;
+	size_t len = 0;
+	FILE *f;
+
+	for (c = 0; c < nr_cpus; c++)
+		counts[c] = 0;
+	f = fopen("/proc/interrupts", "r");
+	if (!f)
+		return false;
+	/* The first line names the CPU of each column. */
+	if (getline(&line, &len, f) < 0)
+		goto out;
+	for (p = line; (p = strstr(p, "CPU")); p = end) {
+		int cpu = strtol(p + 3, &end, 10);
+
+		int *new_col_cpu;
+
+		new_col_cpu = realloc(col_cpu, (nr_cols + 1) * sizeof(*col_cpu));
+		if (!new_col_cpu)
+			goto out;
+		col_cpu = new_col_cpu;
+		col_cpu[nr_cols++] = cpu;
+	}
+	while (getline(&line, &len, f) >= 0) {
+		/* x86 uses the "CAL" label. Others describe the line. */
+		if (!strstr(line, "CAL:") && !strstr(line, "unction call"))
+			continue;
+		p = strchr(line, ':');
+		if (!p)
+			continue;
+		p++;
+		for (i = 0; i < nr_cols; i++) {
+			long val = strtol(p, &end, 10);
+
+			if (end == p)
+				break;
+			p = end;
+			for (c = 0; c < nr_cpus; c++)
+				if (cpus[c] == col_cpu[i]) {
+					counts[c] += val;
+					seen |= 1U << c;
+				}
+		}
+	}
+out:
+	free(col_cpu);
+	free(line);
+	fclose(f);
+	return seen == (1U << nr_cpus) - 1;
+}
+
+/*
+ * Issue @cmd NR_CMDS times. Store into @delta the count of function
+ * call interrupts which each target CPU received meanwhile.
+ */
+static int send_cmds(int cmd, unsigned int flags, int cpu_id, long *delta)
+{
+	long before[MAX_TARGETS], after[MAX_TARGETS];
+	int i;
+
+	if (!read_ipi_counts(target_cpu, MAX_TARGETS, before))
+		return CASE_SKIP_NO_COUNT;
+	for (i = 0; i < NR_CMDS; i++) {
+		if (sys_membarrier(cmd, flags, cpu_id)) {
+			perror("membarrier");
+			return CASE_FAIL;
+		}
+	}
+	if (!read_ipi_counts(target_cpu, MAX_TARGETS, after))
+		return CASE_SKIP_NO_COUNT;
+	for (i = 0; i < MAX_TARGETS; i++)
+		delta[i] = after[i] - before[i];
+	return CASE_PASS;
+}
+
+static int expect_hit(int target, long *delta)
+{
+	if (delta[target] >= MIN_HIT)
+		return CASE_PASS;
+	fprintf(stderr, "# CPU %d received %ld IPIs for %d commands\n",
+		target_cpu[target], delta[target], NR_CMDS);
+	return CASE_FAIL;
+}
+
+static int expect_miss(int target, long *delta)
+{
+	if (delta[target] <= MAX_MISS)
+		return CASE_PASS;
+	fprintf(stderr, "# CPU %d received %ld IPIs, expected none\n",
+		target_cpu[target], delta[target]);
+	return CASE_FAIL;
+}
+
+/*
+ * Global expedited test cases.
+ *
+ * The sender is the test case process. The target is a child process.
+ * @setup runs in the child and ends with spin().
+ */
+
+static void *thread_nop(void *arg)
+{
+	return NULL;
+}
+
+static void *thread_spin(void *arg)
+{
+	pin_to_cpu(target_cpu[(long)arg]);
+	spin();
+	return NULL;
+}
+
+static void setup_registered(void)
+{
+	must_register(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED);
+	spin();
+}
+
+static void setup_unregistered(void)
+{
+	spin();
+}
+
+/*
+ * The exiting thread clears the runqueue membarrier state in
+ * exit_mm(). The scheduler then switches to the main thread, which
+ * has the same mm.
+ */
+static void setup_thread_exit(void)
+{
+	pthread_t thread;
+
+	must_register(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED);
+	if (pthread_create(&thread, NULL, thread_nop, NULL))
+		_exit(CASE_ERROR);
+	must_join(thread);
+	spin();
+}
+
+/*
+ * Register on another CPU. Meanwhile the first CPU is idle and its
+ * idle thread borrows the mm of this process. Come back to the first
+ * CPU: the scheduler switches from the idle thread to this process.
+ */
+static void setup_lazy_tlb(void)
+{
+	pin_to_cpu(target_cpu[1]);
+	must_register(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED);
+	pin_to_cpu(target_cpu[0]);
+	spin();
+}
+
+/* Register from a multi-threaded process. Both threads are targets. */
+static void setup_multi_thread(void)
+{
+	pthread_t thread;
+
+	if (pthread_create(&thread, NULL, thread_spin, (void *)1L))
+		_exit(CASE_ERROR);
+	wait_ready(1);
+	must_register(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED);
+	spin();
+}
+
+/* A forked child inherits the registration. */
+static void setup_fork(void)
+{
+	pid_t pid;
+
+	must_register(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED);
+	pid = fork();
+	if (pid < 0)
+		_exit(CASE_ERROR);
+	if (!pid) {
+		spin();
+		_exit(0);
+	}
+	must_wait(pid);
+}
+
+/* exec() drops the registration. */
+static void setup_exec(void)
+{
+	char fd_str[16];
+
+	must_register(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED);
+	snprintf(fd_str, sizeof(fd_str), "%d", shared_fd);
+	execl("/proc/self/exe", "membarrier_test_ipi", "--spin", fd_str, NULL);
+	perror("execl");
+	_exit(CASE_ERROR);
+}
+
+static int test_global(void (*setup)(void), int nr_targets, bool hit)
+{
+	long delta[MAX_TARGETS];
+	int ret, i;
+	pid_t pid;
+
+	if (!(sys_membarrier(MEMBARRIER_CMD_QUERY, 0, 0) &
+	      MEMBARRIER_CMD_GLOBAL_EXPEDITED))
+		return CASE_SKIP_NO_CMD;
+	pid = fork();
+	if (pid < 0)
+		return CASE_ERROR;
+	if (!pid) {
+		die_with_parent();
+		pin_to_cpu(target_cpu[0]);
+		setup();
+		_exit(0);
+	}
+	wait_ready(nr_targets);
+	ret = send_cmds(MEMBARRIER_CMD_GLOBAL_EXPEDITED, 0, 0, delta);
+	atomic_store(&shared->stop, 1);
+	must_wait(pid);
+	for (i = 0; !ret && i < nr_targets; i++)
+		ret = hit ? expect_hit(i, delta) : expect_miss(i, delta);
+	return ret;
+}
+
+static int case_global_registered(void)
+{
+	return test_global(setup_registered, 1, true);
+}
+
+static int case_global_unregistered(void)
+{
+	return test_global(setup_unregistered, 1, false);
+}
+
+static int case_global_thread_exit(void)
+{
+	return test_global(setup_thread_exit, 1, true);
+}
+
+static int case_global_lazy_tlb(void)
+{
+	return test_global(setup_lazy_tlb, 1, true);
+}
+
+static int case_global_multi_thread(void)
+{
+	return test_global(setup_multi_thread, 2, true);
+}
+
+static int case_global_fork(void)
+{
+	return test_global(setup_fork, 1, true);
+}
+
+static int case_global_exec(void)
+{
+	return test_global(setup_exec, 1, false);
+}
+
+/*
+ * Private expedited test cases.
+ *
+ * The sender is the main thread of the test case process. The targets
+ * are threads of the same process, one per target CPU.
+ */
+
+static int start_thread_targets(pthread_t *threads)
+{
+	long i;
+
+	for (i = 0; i < MAX_TARGETS; i++)
+		if (pthread_create(&threads[i], NULL, thread_spin, (void *)i))
+			return CASE_ERROR;
+	wait_ready(MAX_TARGETS);
+	return CASE_PASS;
+}
+
+static void stop_thread_targets(pthread_t *threads)
+{
+	int i;
+
+	atomic_store(&shared->stop, 1);
+	for (i = 0; i < MAX_TARGETS; i++)
+		must_join(threads[i]);
+}
+
+static int test_private(int register_cmd, int cmd)
+{
+	pthread_t threads[MAX_TARGETS];
+	long delta[MAX_TARGETS];
+	int ret, i;
+
+	if (!(sys_membarrier(MEMBARRIER_CMD_QUERY, 0, 0) & cmd))
+		return CASE_SKIP_NO_CMD;
+	if (start_thread_targets(threads))
+		return CASE_ERROR;
+	must_register(register_cmd);
+	ret = send_cmds(cmd, 0, 0, delta);
+	stop_thread_targets(threads);
+	for (i = 0; !ret && i < MAX_TARGETS; i++)
+		ret = expect_hit(i, delta);
+	return ret;
+}
+
+static int case_private(void)
+{
+	return test_private(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED,
+			    MEMBARRIER_CMD_PRIVATE_EXPEDITED);
+}
+
+static int case_private_sync_core(void)
+{
+	return test_private(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE,
+			    MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE);
+}
+
+static int case_private_rseq(void)
+{
+	return test_private(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ,
+			    MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ);
+}
+
+/* MEMBARRIER_CMD_FLAG_CPU interrupts only the CPU it names. */
+static int case_private_rseq_cpu(void)
+{
+	pthread_t threads[MAX_TARGETS];
+	long delta[MAX_TARGETS];
+	int ret;
+
+	if (!(sys_membarrier(MEMBARRIER_CMD_QUERY, 0, 0) &
+	      MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ))
+		return CASE_SKIP_NO_CMD;
+	if (start_thread_targets(threads))
+		return CASE_ERROR;
+	must_register(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ);
+	ret = send_cmds(MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ,
+			MEMBARRIER_CMD_FLAG_CPU, target_cpu[0], delta);
+	stop_thread_targets(threads);
+	if (!ret)
+		ret = expect_hit(0, delta);
+	if (!ret)
+		ret = expect_miss(1, delta);
+	return ret;
+}
+
+/* A private command does not interrupt another registered process. */
+static int case_private_other_process(void)
+{
+	long delta[MAX_TARGETS];
+	pthread_t thread;
+	pid_t pid;
+	int ret;
+
+	if (!(sys_membarrier(MEMBARRIER_CMD_QUERY, 0, 0) &
+	      MEMBARRIER_CMD_PRIVATE_EXPEDITED))
+		return CASE_SKIP_NO_CMD;
+	pid = fork();
+	if (pid < 0)
+		return CASE_ERROR;
+	if (!pid) {
+		pin_to_cpu(target_cpu[0]);
+		must_register(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED);
+		spin();
+		_exit(0);
+	}
+	if (pthread_create(&thread, NULL, thread_spin, (void *)1L))
+		return CASE_ERROR;
+	wait_ready(2);
+	must_register(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED);
+	ret = send_cmds(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0, delta);
+	atomic_store(&shared->stop, 1);
+	must_join(thread);
+	must_wait(pid);
+	if (!ret)
+		ret = expect_miss(0, delta);
+	if (!ret)
+		ret = expect_hit(1, delta);
+	return ret;
+}
+
+/*
+ * This task shares the TLS of its parent. Its parent pins it, so that
+ * it only spins.
+ */
+static int clone_vm_fn(void *arg)
+{
+	spin();
+	return 0;
+}
+
+/*
+ * A private command covers every task which uses the mm. This includes
+ * a task of another thread group created with CLONE_VM.
+ */
+static int case_private_clone_vm(void)
+{
+	long delta[MAX_TARGETS];
+	char *stack;
+	pid_t pid;
+	int ret;
+
+	if (!(sys_membarrier(MEMBARRIER_CMD_QUERY, 0, 0) &
+	      MEMBARRIER_CMD_PRIVATE_EXPEDITED))
+		return CASE_SKIP_NO_CMD;
+	stack = malloc(STACK_SIZE);
+	if (!stack)
+		return CASE_ERROR;
+	pid = clone(clone_vm_fn, stack + STACK_SIZE, CLONE_VM | SIGCHLD, NULL);
+	if (pid < 0) {
+		free(stack);
+		return CASE_ERROR;
+	}
+	pin_task(pid, target_cpu[0]);
+	wait_ready(1);
+	must_register(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED);
+	ret = send_cmds(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0, delta);
+	atomic_store(&shared->stop, 1);
+	must_wait(pid);
+	free(stack);
+	if (!ret)
+		ret = expect_hit(0, delta);
+	return ret;
+}
+
+static const struct test_case {
+	const char *name;
+	int (*fn)(void);
+} test_cases[] = {
+	{ "global expedited reaches a registered process",
+	  case_global_registered },
+	{ "global expedited skips an unregistered process",
+	  case_global_unregistered },
+	{ "global expedited reaches a process after a thread exit",
+	  case_global_thread_exit },
+	{ "global expedited reaches a process registered on another CPU",
+	  case_global_lazy_tlb },
+	{ "global expedited reaches each thread of a process",
+	  case_global_multi_thread },
+	{ "global expedited reaches a forked child",
+	  case_global_fork },
+	{ "global expedited skips a process after exec",
+	  case_global_exec },
+	{ "private expedited reaches each thread",
+	  case_private },
+	{ "private expedited sync core reaches each thread",
+	  case_private_sync_core },
+	{ "private expedited rseq reaches each thread",
+	  case_private_rseq },
+	{ "private expedited rseq with a CPU reaches only that CPU",
+	  case_private_rseq_cpu },
+	{ "private expedited skips another process",
+	  case_private_other_process },
+	{ "private expedited reaches a CLONE_VM task",
+	  case_private_clone_vm },
+};
+
+static void run_case(const struct test_case *tc)
+{
+	int status;
+	pid_t pid;
+
+	atomic_store(&shared->ready, 0);
+	atomic_store(&shared->stop, 0);
+	pid = fork();
+	if (pid < 0)
+		ksft_exit_fail_msg("fork failed\n");
+	if (!pid) {
+		/* The targets inherit this process group. */
+		if (setpgid(0, 0)) {
+			perror("setpgid");
+			_exit(CASE_ERROR);
+		}
+		die_with_parent();
+		pin_to_cpu(sender_cpu);
+		_exit(tc->fn());
+	}
+	if (waitpid(pid, &status, 0) != pid)
+		ksft_exit_fail_msg("waitpid failed\n");
+	/*
+	 * Kill the targets which a failed test case left behind. There
+	 * is usually none: kill() then fails with ESRCH.
+	 */
+	atomic_store(&shared->stop, 1);
+	if (kill(-pid, SIGKILL) && errno != ESRCH)
+		ksft_exit_fail_msg("kill failed\n");
+	if (!WIFEXITED(status))
+		status = CASE_FAIL;
+	else
+		status = WEXITSTATUS(status);
+	switch (status) {
+	case CASE_PASS:
+		ksft_test_result_pass("%s\n", tc->name);
+		break;
+	case CASE_SKIP_NO_COUNT:
+		ksft_test_result_skip("%s: Cannot read the interrupt counts\n",
+				      tc->name);
+		break;
+	case CASE_SKIP_NO_CMD:
+		ksft_test_result_skip("%s: Command not available\n",
+				      tc->name);
+		break;
+	case CASE_ERROR:
+		ksft_test_result_error("%s: Test setup failed\n", tc->name);
+		break;
+	default:
+		ksft_test_result_fail("%s\n", tc->name);
+	}
+}
+
+/* Pick the sender CPU and the target CPUs among the allowed CPUs. */
+static bool pick_cpus(void)
+{
+	int cpu, nr = 0, *dst[] = {
+		&sender_cpu, &target_cpu[0], &target_cpu[1],
+	};
+	cpu_set_t set;
+
+	if (sched_getaffinity(0, sizeof(set), &set))
+		return false;
+	for (cpu = 0; cpu < CPU_SETSIZE && nr < ARRAY_SIZE(dst); cpu++)
+		if (CPU_ISSET(cpu, &set))
+			*dst[nr++] = cpu;
+	return nr == ARRAY_SIZE(dst);
+}
+
+static struct shared *map_shared(int fd)
+{
+	void *p;
+
+	p = mmap(NULL, sizeof(struct shared), PROT_READ | PROT_WRITE,
+		 MAP_SHARED, fd, 0);
+	return p == MAP_FAILED ? NULL : p;
+}
+
+int main(int argc, char **argv)
+{
+	long counts[MAX_TARGETS];
+	int i;
+
+	/* Target of the exec test case. See setup_exec(). */
+	if (argc == 3 && !strcmp(argv[1], "--spin")) {
+		shared = map_shared(atoi(argv[2]));
+		if (!shared)
+			return CASE_ERROR;
+		spin();
+		return 0;
+	}
+
+	ksft_print_header();
+	if (sys_membarrier(MEMBARRIER_CMD_QUERY, 0, 0) < 0)
+		ksft_exit_skip("sys membarrier (CONFIG_MEMBARRIER) is disabled.\n");
+	if (!pick_cpus())
+		ksft_exit_skip("This test needs 3 allowed CPUs.\n");
+	if (!read_ipi_counts(target_cpu, MAX_TARGETS, counts))
+		ksft_exit_skip("No function call interrupt count in /proc/interrupts.\n");
+	shared_fd = memfd_create("membarrier_test_ipi", 0);
+	if (shared_fd < 0 || ftruncate(shared_fd, sizeof(struct shared)))
+		ksft_exit_fail_msg("memfd_create failed\n");
+	shared = map_shared(shared_fd);
+	if (!shared)
+		ksft_exit_fail_msg("mmap failed\n");
+
+	ksft_set_plan(ARRAY_SIZE(test_cases));
+	for (i = 0; i < ARRAY_SIZE(test_cases); i++)
+		run_case(&test_cases[i]);
+	ksft_finished();
+}
-- 
2.47.3


  parent reply	other threads:[~2026-11-13 13:19 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-13 13:18 [RFC PATCH 00/16] membarrier: fixes, selftests and documentation Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 01/16] sched/membarrier: Fix stale runqueue state after a lazy mm switch Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 02/16] sched/membarrier: Report only completed registrations Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 03/16] selftests/membarrier: Rebuild the tests when their header changes Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 04/16] selftests/membarrier: Issue the sync core command in its test Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 05/16] selftests/membarrier: Run the tests on nohz_full kernels Mathieu Desnoyers
2026-11-13 13:18 ` Mathieu Desnoyers [this message]
2026-11-13 13:18 ` [RFC PATCH 07/16] selftests/membarrier: Test command arguments and registration lifetime Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 08/16] LoongArch: membarrier: Add full memory barrier in switch_mm() Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 09/16] parisc: " Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 10/16] Documentation/membarrier: Update the per-architecture barrier list Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 11/16] Documentation/features: Update the membarrier sync core requirements Mathieu Desnoyers
2026-11-13 13:18 ` [RFC PATCH 12/16] x86/entry: Remove stale membarrier sync core comments Mathieu Desnoyers
2026-11-13 13:19 ` [RFC PATCH 13/16] selftests/membarrier: Add litmus tests of the memory ordering Mathieu Desnoyers
2026-11-13 13:19 ` [RFC PATCH 14/16] MAINTAINERS: Add the membarrier selftests to MEMBARRIER SUPPORT Mathieu Desnoyers
2026-11-13 13:19 ` [RFC PATCH 15/16] selftests/membarrier: Check the memory barriers of each thread Mathieu Desnoyers
2026-11-13 13:19 ` [RFC PATCH 16/16] selftests/membarrier: Check the core serialization of the sync core command Mathieu Desnoyers

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261113131903.157186-7-mathieu.desnoyers@efficios.com \
    --to=mathieu.desnoyers@efficios.com \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=akiyks@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=alex@ghiti.fr \
    --cc=aniketgattani@google.com \
    --cc=aou@eecs.berkeley.edu \
    --cc=avagin@gmail.com \
    --cc=boqun@kernel.org \
    --cc=bp@alien8.de \
    --cc=bpf@vger.kernel.org \
    --cc=bsegall@google.com \
    --cc=catalin.marinas@arm.com \
    --cc=chenhuacai@kernel.org \
    --cc=chris@zankel.net \
    --cc=christian.gellermann@codasip.com \
    --cc=corbet@lwn.net \
    --cc=dalias@libc.org \
    --cc=dave.anglin@bell.net \
    --cc=dave.hansen@linux.intel.com \
    --cc=davem@davemloft.net \
    --cc=deller@gmx.de \
    --cc=dhowells@redhat.com \
    --cc=dietmar.eggemann@arm.com \
    --cc=dlustig@nvidia.com \
    --cc=glaubitz@physik.fu-berlin.de \
    --cc=guoren@kernel.org \
    --cc=hpa@zytor.com \
    --cc=j.alglave@ucl.ac.uk \
    --cc=jcmvbkbc@gmail.com \
    --cc=joelagnelf@nvidia.com \
    --cc=jonas@southpole.se \
    --cc=juri.lelli@redhat.com \
    --cc=kernel@xen0n.name \
    --cc=kprateek.nayak@amd.com \
    --cc=linux-arch@vger.kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-csky@vger.kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-openrisc@vger.kernel.org \
    --cc=linux-parisc@vger.kernel.org \
    --cc=linux-riscv@lists.infradead.org \
    --cc=linux-sh@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=lkmm@lists.linux.dev \
    --cc=loongarch@lists.linux.dev \
    --cc=luc.maranget@inria.fr \
    --cc=luto@kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mclapinski@google.com \
    --cc=mgorman@suse.de \
    --cc=mhiramat@kernel.org \
    --cc=mingo@redhat.com \
    --cc=naveen@kernel.org \
    --cc=npiggin@gmail.com \
    --cc=palmer@dabbelt.com \
    --cc=parri.andrea@gmail.com \
    --cc=paulmck@kernel.org \
    --cc=peterz@infradead.org \
    --cc=pjw@kernel.org \
    --cc=rdunlap@infradead.org \
    --cc=reddybalavignesh9979@gmail.com \
    --cc=richard.weiyang@gmail.com \
    --cc=rostedt@goodmis.org \
    --cc=shorne@gmail.com \
    --cc=shuah@kernel.org \
    --cc=skhan@linuxfoundation.org \
    --cc=stefan.kristiansson@saunalahti.fi \
    --cc=stern@rowland.harvard.edu \
    --cc=tglx@kernel.org \
    --cc=vincent.guittot@linaro.org \
    --cc=vschneid@redhat.com \
    --cc=will@kernel.org \
    --cc=x86@kernel.org \
    --cc=ysato@users.sourceforge.jp \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®