[RFC PATCH 06/16] selftests/membarrier: Test which CPUs the expedited commands interrupt
Mathieu Desnoyers
mathieu.desnoyers at efficios.com
Fri Nov 13 05:18:53 PST 2026
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 at 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
More information about the linux-riscv
mailing list