]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/rseq: Implement time slice extension test
authorThomas Gleixner <tglx@linutronix.de>
Mon, 15 Dec 2025 16:52:34 +0000 (17:52 +0100)
committerPeter Zijlstra <peterz@infradead.org>
Thu, 22 Jan 2026 10:11:19 +0000 (11:11 +0100)
Provide an initial test case to evaluate the functionality. This needs to be
extended to cover the ABI violations and expose the race condition between
observing granted and arriving in rseq_slice_yield().

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251215155709.320325431@linutronix.de
tools/testing/selftests/rseq/.gitignore
tools/testing/selftests/rseq/Makefile
tools/testing/selftests/rseq/rseq-abi.h
tools/testing/selftests/rseq/slice_test.c [new file with mode: 0644]

index 0fda241fa62b0295feef918a97f75061021f39c1..ec01d164c1f05570607c8df902096249ae3175c4 100644 (file)
@@ -10,3 +10,4 @@ param_test_mm_cid
 param_test_mm_cid_benchmark
 param_test_mm_cid_compare_twice
 syscall_errors_test
+slice_test
index 0d0a5fae59547fead6c543a7cfef9af38839f009..4ef90823b6526f9b2d39bf87f302af02402201b5 100644 (file)
@@ -17,7 +17,7 @@ OVERRIDE_TARGETS = 1
 TEST_GEN_PROGS = basic_test basic_percpu_ops_test basic_percpu_ops_mm_cid_test param_test \
                param_test_benchmark param_test_compare_twice param_test_mm_cid \
                param_test_mm_cid_benchmark param_test_mm_cid_compare_twice \
-               syscall_errors_test
+               syscall_errors_test slice_test
 
 TEST_GEN_PROGS_EXTENDED = librseq.so
 
@@ -59,3 +59,6 @@ $(OUTPUT)/param_test_mm_cid_compare_twice: param_test.c $(TEST_GEN_PROGS_EXTENDE
 $(OUTPUT)/syscall_errors_test: syscall_errors_test.c $(TEST_GEN_PROGS_EXTENDED) \
                                        rseq.h rseq-*.h
        $(CC) $(CFLAGS) $< $(LDLIBS) -lrseq -o $@
+
+$(OUTPUT)/slice_test: slice_test.c $(TEST_GEN_PROGS_EXTENDED) rseq.h rseq-*.h
+       $(CC) $(CFLAGS) $< $(LDLIBS) -lrseq -o $@
index fb4ec8a75dd48bb4813a3232e4403b8cc5337ef6..ecef315204b271f50bccf8f2a847986357fd6f2e 100644 (file)
@@ -53,6 +53,27 @@ struct rseq_abi_cs {
        __u64 abort_ip;
 } __attribute__((aligned(4 * sizeof(__u64))));
 
+/**
+ * rseq_abi_slice_ctrl - Time slice extension control structure
+ * @all:       Compound value
+ * @request:   Request for a time slice extension
+ * @granted:   Granted time slice extension
+ *
+ * @request is set by user space and can be cleared by user space or kernel
+ * space.  @granted is set and cleared by the kernel and must only be read
+ * by user space.
+ */
+struct rseq_abi_slice_ctrl {
+       union {
+               __u32           all;
+               struct {
+                       __u8    request;
+                       __u8    granted;
+                       __u16   __reserved;
+               };
+       };
+};
+
 /*
  * struct rseq_abi is aligned on 4 * 8 bytes to ensure it is always
  * contained within a single cache-line.
@@ -164,6 +185,12 @@ struct rseq_abi {
         */
        __u32 mm_cid;
 
+       /*
+        * Time slice extension control structure. CPU local updates from
+        * kernel and user space.
+        */
+       struct rseq_abi_slice_ctrl slice_ctrl;
+
        /*
         * Flexible array member at end of structure, after last feature field.
         */
diff --git a/tools/testing/selftests/rseq/slice_test.c b/tools/testing/selftests/rseq/slice_test.c
new file mode 100644 (file)
index 0000000..357122d
--- /dev/null
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: LGPL-2.1
+#define _GNU_SOURCE
+#include <assert.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include <unistd.h>
+
+#include <linux/prctl.h>
+#include <sys/prctl.h>
+#include <sys/time.h>
+
+#include "rseq.h"
+
+#include "../kselftest_harness.h"
+
+#ifndef __NR_rseq_slice_yield
+# define __NR_rseq_slice_yield 471
+#endif
+
+#define BITS_PER_INT   32
+#define BITS_PER_BYTE  8
+
+#ifndef PR_RSEQ_SLICE_EXTENSION
+# define PR_RSEQ_SLICE_EXTENSION               79
+#  define PR_RSEQ_SLICE_EXTENSION_GET          1
+#  define PR_RSEQ_SLICE_EXTENSION_SET          2
+#  define PR_RSEQ_SLICE_EXT_ENABLE             0x01
+#endif
+
+#ifndef RSEQ_SLICE_EXT_REQUEST_BIT
+# define RSEQ_SLICE_EXT_REQUEST_BIT    0
+# define RSEQ_SLICE_EXT_GRANTED_BIT    1
+#endif
+
+#ifndef asm_inline
+# define asm_inline    asm __inline
+#endif
+
+#define NSEC_PER_SEC   1000000000L
+#define NSEC_PER_USEC        1000L
+
+struct noise_params {
+       int64_t noise_nsecs;
+       int64_t sleep_nsecs;
+       int64_t run;
+};
+
+FIXTURE(slice_ext)
+{
+       pthread_t               noise_thread;
+       struct noise_params     noise_params;
+};
+
+FIXTURE_VARIANT(slice_ext)
+{
+       int64_t total_nsecs;
+       int64_t slice_nsecs;
+       int64_t noise_nsecs;
+       int64_t sleep_nsecs;
+       bool    no_yield;
+};
+
+FIXTURE_VARIANT_ADD(slice_ext, n2_2_50)
+{
+       .total_nsecs    =  5LL * NSEC_PER_SEC,
+       .slice_nsecs    =  2LL * NSEC_PER_USEC,
+       .noise_nsecs    =  2LL * NSEC_PER_USEC,
+       .sleep_nsecs    = 50LL * NSEC_PER_USEC,
+};
+
+FIXTURE_VARIANT_ADD(slice_ext, n50_2_50)
+{
+       .total_nsecs    =  5LL * NSEC_PER_SEC,
+       .slice_nsecs    = 50LL * NSEC_PER_USEC,
+       .noise_nsecs    =  2LL * NSEC_PER_USEC,
+       .sleep_nsecs    = 50LL * NSEC_PER_USEC,
+};
+
+FIXTURE_VARIANT_ADD(slice_ext, n2_2_50_no_yield)
+{
+       .total_nsecs    =  5LL * NSEC_PER_SEC,
+       .slice_nsecs    =  2LL * NSEC_PER_USEC,
+       .noise_nsecs    =  2LL * NSEC_PER_USEC,
+       .sleep_nsecs    = 50LL * NSEC_PER_USEC,
+       .no_yield       = true,
+};
+
+
+static inline bool elapsed(struct timespec *start, struct timespec *now,
+                          int64_t span)
+{
+       int64_t delta = now->tv_sec - start->tv_sec;
+
+       delta *= NSEC_PER_SEC;
+       delta += now->tv_nsec - start->tv_nsec;
+       return delta >= span;
+}
+
+static void *noise_thread(void *arg)
+{
+       struct noise_params *p = arg;
+
+       while (RSEQ_READ_ONCE(p->run)) {
+               struct timespec ts_start, ts_now;
+
+               clock_gettime(CLOCK_MONOTONIC, &ts_start);
+               do {
+                       clock_gettime(CLOCK_MONOTONIC, &ts_now);
+               } while (!elapsed(&ts_start, &ts_now, p->noise_nsecs));
+
+               ts_start.tv_sec = 0;
+               ts_start.tv_nsec = p->sleep_nsecs;
+               clock_nanosleep(CLOCK_MONOTONIC, 0, &ts_start, NULL);
+       }
+       return NULL;
+}
+
+FIXTURE_SETUP(slice_ext)
+{
+       cpu_set_t affinity;
+
+       ASSERT_EQ(sched_getaffinity(0, sizeof(affinity), &affinity), 0);
+
+       /* Pin it on a single CPU. Avoid CPU 0 */
+       for (int i = 1; i < CPU_SETSIZE; i++) {
+               if (!CPU_ISSET(i, &affinity))
+                       continue;
+
+               CPU_ZERO(&affinity);
+               CPU_SET(i, &affinity);
+               ASSERT_EQ(sched_setaffinity(0, sizeof(affinity), &affinity), 0);
+               break;
+       }
+
+       ASSERT_EQ(rseq_register_current_thread(), 0);
+
+       ASSERT_EQ(prctl(PR_RSEQ_SLICE_EXTENSION, PR_RSEQ_SLICE_EXTENSION_SET,
+                       PR_RSEQ_SLICE_EXT_ENABLE, 0, 0), 0);
+
+       self->noise_params.noise_nsecs = variant->noise_nsecs;
+       self->noise_params.sleep_nsecs = variant->sleep_nsecs;
+       self->noise_params.run = 1;
+
+       ASSERT_EQ(pthread_create(&self->noise_thread, NULL, noise_thread, &self->noise_params), 0);
+}
+
+FIXTURE_TEARDOWN(slice_ext)
+{
+       self->noise_params.run = 0;
+       pthread_join(self->noise_thread, NULL);
+}
+
+TEST_F(slice_ext, slice_test)
+{
+       unsigned long success = 0, yielded = 0, scheduled = 0, raced = 0;
+       unsigned long total = 0, aborted = 0;
+       struct rseq_abi *rs = rseq_get_abi();
+       struct timespec ts_start, ts_now;
+
+       ASSERT_NE(rs, NULL);
+
+       clock_gettime(CLOCK_MONOTONIC, &ts_start);
+       do {
+               struct timespec ts_cs;
+               bool req = false;
+
+               clock_gettime(CLOCK_MONOTONIC, &ts_cs);
+
+               total++;
+               RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 1);
+               do {
+                       clock_gettime(CLOCK_MONOTONIC, &ts_now);
+               } while (!elapsed(&ts_cs, &ts_now, variant->slice_nsecs));
+
+               /*
+                * request can be cleared unconditionally, but for making
+                * the stats work this is actually checking it first
+                */
+               if (RSEQ_READ_ONCE(rs->slice_ctrl.request)) {
+                       RSEQ_WRITE_ONCE(rs->slice_ctrl.request, 0);
+                       /* Race between check and clear! */
+                       req = true;
+                       success++;
+               }
+
+               if (RSEQ_READ_ONCE(rs->slice_ctrl.granted)) {
+                       /* The above raced against a late grant */
+                       if (req)
+                               success--;
+                       if (variant->no_yield) {
+                               syscall(__NR_getpid);
+                               aborted++;
+                       } else {
+                               yielded++;
+                               if (!syscall(__NR_rseq_slice_yield))
+                                       raced++;
+                       }
+               } else {
+                       if (!req)
+                               scheduled++;
+               }
+
+               clock_gettime(CLOCK_MONOTONIC, &ts_now);
+       } while (!elapsed(&ts_start, &ts_now, variant->total_nsecs));
+
+       printf("# Total     %12ld\n", total);
+       printf("# Success   %12ld\n", success);
+       printf("# Yielded   %12ld\n", yielded);
+       printf("# Aborted   %12ld\n", aborted);
+       printf("# Scheduled %12ld\n", scheduled);
+       printf("# Raced     %12ld\n", raced);
+}
+
+TEST_HARNESS_MAIN