]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/x86/xstate: Refactor context switching test
authorChang S. Bae <chang.seok.bae@intel.com>
Wed, 26 Feb 2025 01:07:24 +0000 (17:07 -0800)
committerIngo Molnar <mingo@kernel.org>
Wed, 26 Feb 2025 12:05:29 +0000 (13:05 +0100)
The existing context switching and ptrace tests in amx.c are not specific
to dynamic states, making them reusable for general xstate testing.

As a first step, move the context switching test to xstate.c. Refactor
the test code to allow specifying which xstate component being tested.

To decouple the test from dynamic states, remove the permission request
code. In fact, The permission request inside the test wrapper was
redundant.

Additionally, replace fatal_error() with ksft_exit_fail_msg() for
consistency in error handling.

  Expected output:
  $ amx_64
  ...
  [RUN]   AMX Tile data: check context switches, 10 iterations, 5 threads.
  [OK]    No incorrect case was found.

Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Link: https://lore.kernel.org/r/20250226010731.2456-5-chang.seok.bae@intel.com
tools/testing/selftests/x86/Makefile
tools/testing/selftests/x86/amx.c
tools/testing/selftests/x86/xstate.c [new file with mode: 0644]
tools/testing/selftests/x86/xstate.h

index d51249f14e2fe8b0efd9c835e23b317507a7af5d..f15efdc6aef7310b8312c06cf882c739262f7240 100644 (file)
@@ -132,3 +132,5 @@ $(OUTPUT)/check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static
 
 $(OUTPUT)/nx_stack_32: CFLAGS += -Wl,-z,noexecstack
 $(OUTPUT)/nx_stack_64: CFLAGS += -Wl,-z,noexecstack
+
+$(OUTPUT)/amx_64: EXTRA_FILES += xstate.c
index cde22f303905622959bbcdf9a6ed04329deb4294..b3c51dd25abca7e7b6dee31aac095ae6e23721bc 100644 (file)
@@ -3,7 +3,6 @@
 #define _GNU_SOURCE
 #include <err.h>
 #include <errno.h>
-#include <pthread.h>
 #include <setjmp.h>
 #include <stdio.h>
 #include <string.h>
@@ -434,14 +433,6 @@ static inline bool __validate_tiledata_regs(struct xsave_buffer *xbuf1)
        return true;
 }
 
-static inline void validate_tiledata_regs_same(struct xsave_buffer *xbuf)
-{
-       int ret = __validate_tiledata_regs(xbuf);
-
-       if (ret != 0)
-               fatal_error("TILEDATA registers changed");
-}
-
 static inline void validate_tiledata_regs_changed(struct xsave_buffer *xbuf)
 {
        int ret = __validate_tiledata_regs(xbuf);
@@ -498,158 +489,6 @@ static void test_fork(void)
        _exit(0);
 }
 
-/* Context switching test */
-
-static struct _ctxtswtest_cfg {
-       unsigned int iterations;
-       unsigned int num_threads;
-} ctxtswtest_config;
-
-struct futex_info {
-       pthread_t thread;
-       int nr;
-       pthread_mutex_t mutex;
-       struct futex_info *next;
-};
-
-static void *check_tiledata(void *info)
-{
-       struct futex_info *finfo = (struct futex_info *)info;
-       struct xsave_buffer *xbuf;
-       int i;
-
-       xbuf = alloc_xbuf();
-       if (!xbuf)
-               fatal_error("unable to allocate XSAVE buffer");
-
-       /*
-        * Load random data into 'xbuf' and then restore
-        * it to the tile registers themselves.
-        */
-       load_rand_tiledata(xbuf);
-       for (i = 0; i < ctxtswtest_config.iterations; i++) {
-               pthread_mutex_lock(&finfo->mutex);
-
-               /*
-                * Ensure the register values have not
-                * diverged from those recorded in 'xbuf'.
-                */
-               validate_tiledata_regs_same(xbuf);
-
-               /* Load new, random values into xbuf and registers */
-               load_rand_tiledata(xbuf);
-
-               /*
-                * The last thread's last unlock will be for
-                * thread 0's mutex.  However, thread 0 will
-                * have already exited the loop and the mutex
-                * will already be unlocked.
-                *
-                * Because this is not an ERRORCHECK mutex,
-                * that inconsistency will be silently ignored.
-                */
-               pthread_mutex_unlock(&finfo->next->mutex);
-       }
-
-       free(xbuf);
-       /*
-        * Return this thread's finfo, which is
-        * a unique value for this thread.
-        */
-       return finfo;
-}
-
-static int create_threads(int num, struct futex_info *finfo)
-{
-       int i;
-
-       for (i = 0; i < num; i++) {
-               int next_nr;
-
-               finfo[i].nr = i;
-               /*
-                * Thread 'i' will wait on this mutex to
-                * be unlocked.  Lock it immediately after
-                * initialization:
-                */
-               pthread_mutex_init(&finfo[i].mutex, NULL);
-               pthread_mutex_lock(&finfo[i].mutex);
-
-               next_nr = (i + 1) % num;
-               finfo[i].next = &finfo[next_nr];
-
-               if (pthread_create(&finfo[i].thread, NULL, check_tiledata, &finfo[i]))
-                       fatal_error("pthread_create()");
-       }
-       return 0;
-}
-
-static void affinitize_cpu0(void)
-{
-       cpu_set_t cpuset;
-
-       CPU_ZERO(&cpuset);
-       CPU_SET(0, &cpuset);
-
-       if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
-               fatal_error("sched_setaffinity to CPU 0");
-}
-
-static void test_context_switch(void)
-{
-       struct futex_info *finfo;
-       int i;
-
-       /* Affinitize to one CPU to force context switches */
-       affinitize_cpu0();
-
-       req_xtiledata_perm();
-
-       printf("[RUN]\tCheck tiledata context switches, %d iterations, %d threads.\n",
-              ctxtswtest_config.iterations,
-              ctxtswtest_config.num_threads);
-
-
-       finfo = malloc(sizeof(*finfo) * ctxtswtest_config.num_threads);
-       if (!finfo)
-               fatal_error("malloc()");
-
-       create_threads(ctxtswtest_config.num_threads, finfo);
-
-       /*
-        * This thread wakes up thread 0
-        * Thread 0 will wake up 1
-        * Thread 1 will wake up 2
-        * ...
-        * the last thread will wake up 0
-        *
-        * ... this will repeat for the configured
-        * number of iterations.
-        */
-       pthread_mutex_unlock(&finfo[0].mutex);
-
-       /* Wait for all the threads to finish: */
-       for (i = 0; i < ctxtswtest_config.num_threads; i++) {
-               void *thread_retval;
-               int rc;
-
-               rc = pthread_join(finfo[i].thread, &thread_retval);
-
-               if (rc)
-                       fatal_error("pthread_join() failed for thread %d err: %d\n",
-                                       i, rc);
-
-               if (thread_retval != &finfo[i])
-                       fatal_error("unexpected thread retval for thread %d: %p\n",
-                                       i, thread_retval);
-
-       }
-
-       printf("[OK]\tNo incorrect case was found.\n");
-
-       free(finfo);
-}
-
 /* Ptrace test */
 
 /*
@@ -745,6 +584,7 @@ static void test_ptrace(void)
 
 int main(void)
 {
+       const unsigned int ctxtsw_num_threads = 5, ctxtsw_iterations = 10;
        unsigned long features;
        long rc;
 
@@ -772,9 +612,7 @@ int main(void)
 
        test_fork();
 
-       ctxtswtest_config.iterations = 10;
-       ctxtswtest_config.num_threads = 5;
-       test_context_switch();
+       test_context_switch(XFEATURE_XTILEDATA, ctxtsw_num_threads, ctxtsw_iterations);
 
        test_ptrace();
 
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
new file mode 100644 (file)
index 0000000..e5b51e7
--- /dev/null
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include <pthread.h>
+#include <stdbool.h>
+
+#include "helpers.h"
+#include "xstate.h"
+
+static struct xstate_info xstate;
+
+struct futex_info {
+       unsigned int iterations;
+       struct futex_info *next;
+       pthread_mutex_t mutex;
+       pthread_t thread;
+       bool valid;
+       int nr;
+};
+
+static inline void load_rand_xstate(struct xstate_info *xstate, struct xsave_buffer *xbuf)
+{
+       clear_xstate_header(xbuf);
+       set_xstatebv(xbuf, xstate->mask);
+       set_rand_data(xstate, xbuf);
+       xrstor(xbuf, xstate->mask);
+}
+
+static inline bool validate_xstate_same(struct xsave_buffer *xbuf1, struct xsave_buffer *xbuf2)
+{
+       int ret;
+
+       ret = memcmp(&xbuf1->bytes[xstate.xbuf_offset],
+                    &xbuf2->bytes[xstate.xbuf_offset],
+                    xstate.size);
+       return ret == 0;
+}
+
+static inline bool validate_xregs_same(struct xsave_buffer *xbuf1)
+{
+       struct xsave_buffer *xbuf2;
+       bool ret;
+
+       xbuf2 = alloc_xbuf();
+       if (!xbuf2)
+               ksft_exit_fail_msg("failed to allocate XSAVE buffer\n");
+
+       xsave(xbuf2, xstate.mask);
+       ret = validate_xstate_same(xbuf1, xbuf2);
+
+       free(xbuf2);
+       return ret;
+}
+
+/* Context switching test */
+
+static void *check_xstate(void *info)
+{
+       struct futex_info *finfo = (struct futex_info *)info;
+       struct xsave_buffer *xbuf;
+       int i;
+
+       xbuf = alloc_xbuf();
+       if (!xbuf)
+               ksft_exit_fail_msg("unable to allocate XSAVE buffer\n");
+
+       /*
+        * Load random data into 'xbuf' and then restore it to the xstate
+        * registers.
+        */
+       load_rand_xstate(&xstate, xbuf);
+       finfo->valid = true;
+
+       for (i = 0; i < finfo->iterations; i++) {
+               pthread_mutex_lock(&finfo->mutex);
+
+               /*
+                * Ensure the register values have not diverged from the
+                * record. Then reload a new random value.  If it failed
+                * ever before, skip it.
+                */
+               if (finfo->valid) {
+                       finfo->valid = validate_xregs_same(xbuf);
+                       load_rand_xstate(&xstate, xbuf);
+               }
+
+               /*
+                * The last thread's last unlock will be for thread 0's
+                * mutex. However, thread 0 will have already exited the
+                * loop and the mutex will already be unlocked.
+                *
+                * Because this is not an ERRORCHECK mutex, that
+                * inconsistency will be silently ignored.
+                */
+               pthread_mutex_unlock(&finfo->next->mutex);
+       }
+
+       free(xbuf);
+       return finfo;
+}
+
+static void create_threads(uint32_t num_threads, uint32_t iterations, struct futex_info *finfo)
+{
+       int i;
+
+       for (i = 0; i < num_threads; i++) {
+               int next_nr;
+
+               finfo[i].nr = i;
+               finfo[i].iterations = iterations;
+
+               /*
+                * Thread 'i' will wait on this mutex to be unlocked.
+                * Lock it immediately after initialization:
+                */
+               pthread_mutex_init(&finfo[i].mutex, NULL);
+               pthread_mutex_lock(&finfo[i].mutex);
+
+               next_nr = (i + 1) % num_threads;
+               finfo[i].next = &finfo[next_nr];
+
+               if (pthread_create(&finfo[i].thread, NULL, check_xstate, &finfo[i]))
+                       ksft_exit_fail_msg("pthread_create() failed\n");
+       }
+}
+
+static bool checkout_threads(uint32_t num_threads, struct futex_info *finfo)
+{
+       void *thread_retval;
+       bool valid = true;
+       int err, i;
+
+       for (i = 0; i < num_threads; i++) {
+               err = pthread_join(finfo[i].thread, &thread_retval);
+               if (err)
+                       ksft_exit_fail_msg("pthread_join() failed for thread %d err: %d\n", i, err);
+
+               if (thread_retval != &finfo[i]) {
+                       ksft_exit_fail_msg("unexpected thread retval for thread %d: %p\n",
+                                          i, thread_retval);
+               }
+
+               valid &= finfo[i].valid;
+       }
+
+       return valid;
+}
+
+static void affinitize_cpu0(void)
+{
+       cpu_set_t cpuset;
+
+       CPU_ZERO(&cpuset);
+       CPU_SET(0, &cpuset);
+
+       if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
+               ksft_exit_fail_msg("sched_setaffinity to CPU 0 failed\n");
+}
+
+void test_context_switch(uint32_t feature_num, uint32_t num_threads, uint32_t iterations)
+{
+       struct futex_info *finfo;
+
+       /* Affinitize to one CPU to force context switches */
+       affinitize_cpu0();
+
+       xstate = get_xstate_info(feature_num);
+
+       printf("[RUN]\t%s: check context switches, %d iterations, %d threads.\n",
+              xstate.name, iterations, num_threads);
+
+       finfo = malloc(sizeof(*finfo) * num_threads);
+       if (!finfo)
+               ksft_exit_fail_msg("unable allocate memory\n");
+
+       create_threads(num_threads, iterations, finfo);
+
+       /*
+        * This thread wakes up thread 0
+        * Thread 0 will wake up 1
+        * Thread 1 will wake up 2
+        * ...
+        * The last thread will wake up 0
+        *
+        * This will repeat for the configured
+        * number of iterations.
+        */
+       pthread_mutex_unlock(&finfo[0].mutex);
+
+       /* Wait for all the threads to finish: */
+       if (checkout_threads(num_threads, finfo))
+               printf("[OK]\tNo incorrect case was found.\n");
+       else
+               printf("[FAIL]\tFailed with context switching test.\n");
+
+       free(finfo);
+}
index d3461c4384614538ab63d30a8ae4e30cbe4d3287..5ef66f247eb92834468edfd440e3f5f73c4b5cc2 100644 (file)
@@ -189,4 +189,6 @@ static inline void set_rand_data(struct xstate_info *xstate, struct xsave_buffer
                *ptr = data;
 }
 
+void test_context_switch(uint32_t feature_num, uint32_t num_threads, uint32_t iterations);
+
 #endif /* __SELFTESTS_X86_XSTATE_H */