diff --git a/tools/testing/selftests/x86/.gitignore b/tools/testing/selftests/x86/.gitignore
index 1aaef5bf119a..68951ceefe30 100644
--- a/tools/testing/selftests/x86/.gitignore
+++ b/tools/testing/selftests/x86/.gitignore
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 *_32
 *_64
+*.o
 single_step_syscall
 sysret_ss_attrs
 syscall_nt
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 0388c4d60af0..49a6d78e0831 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -18,7 +18,7 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
 			test_FCMOV test_FCOMI test_FISTTP \
 			vdso_restorer
 TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
-			corrupt_xstate_header amx
+			corrupt_xstate_header amx xstate
 # Some selftests require 32bit support enabled also on 64bit systems
 TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
 
@@ -69,7 +69,7 @@ all_32: $(BINARIES_32)
 
 all_64: $(BINARIES_64)
 
-EXTRA_CLEAN := $(BINARIES_32) $(BINARIES_64)
+EXTRA_CLEAN := $(BINARIES_32) $(BINARIES_64) *.o
 
 $(BINARIES_32): $(OUTPUT)/%_32: %.c helpers.h
 	$(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl -lm
@@ -109,3 +109,10 @@ $(OUTPUT)/test_syscall_vdso_32: thunks_32.S
 # state.
 $(OUTPUT)/check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static
 $(OUTPUT)/check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static
+
+# xstate_64 is special: it needs xstate_helpers.o to prevent GCC from
+# generating any FP code by mistake and stdlib.h can't be used due to
+# "-mno-sse" parameter, so compile xstate_64 with the code file xstate.c
+# which can use stdlib.h and xstate_helpers.o which cannot use stdlib.h
+xstate_helpers.o: CFLAGS += -mno-sse -mno-mmx -mno-sse2 -mno-avx -mno-pku
+$(OUTPUT)/xstate_64: xstate_helpers.o
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
new file mode 100644
index 000000000000..f325c6cd2776
--- /dev/null
+++ b/tools/testing/selftests/x86/xstate.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * xstate.c - tests XSAVE feature with fork and signal handling.
+ *
+ * The XSAVE feature set supports the saving and restoring of state components.
+ * It tests "FP, SSE(XMM), AVX2(YMM), AVX512_OPMASK/AVX512_ZMM_Hi256/
+ * AVX512_Hi16_ZMM and PKRU parts" xstates with following cases:
+ * 1. The contents of these xstates in the process should not change after the
+ *    signal handling.
+ * 2. The contents of these xstates in the child process should be the same as
+ *    the contents of the xstate in the parent process after the fork syscall.
+ * 3. The contents of xstates in the parent process should not change after
+ *    the context switch.
+ *
+ * The regions and reserved bytes of the components tested for XSAVE feature
+ * are as follows:
+ * x87(FP)/SSE    (0 - 159 bytes)
+ * SSE(XMM part)  (160-415 bytes)
+ * Reserved       (416-511 bytes)
+ * Header_used    (512-527 bytes; XSTATE BV(bitmap vector) mask:512-519 bytes)
+ * Header_reserved(528-575 bytes must be 00)
+ * YMM            (Offset:CPUID.(EAX=0D,ECX=2).EBX Size:CPUID(EAX=0D,ECX=2).EAX)
+ * AVX512_OPMASK  (Offset:CPUID.(EAX=0D,ECX=5).EBX Size:CPUID(EAX=0D,ECX=5).EAX)
+ * ZMM_Hi256      (Offset:CPUID.(EAX=0D,ECX=6).EBX Size:CPUID(EAX=0D,ECX=6).EAX)
+ * Hi16_ZMM       (Offset:CPUID.(EAX=0D,ECX=7).EBX Size:CPUID(EAX=0D,ECX=7).EAX)
+ * PKRU           (Offset:CPUID.(EAX=0D,ECX=9).EBX Size:CPUID(EAX=0D,ECX=9).EAX)
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <malloc.h>
+#include <stdlib.h>
+
+#include "xstate.h"
+#include "xstate_helpers.h"
+#include "../kselftest.h"
+
+#define NUM_TESTS	1
+#define xstate_test_array_init(idx, init_opt, fill_opt)	\
+	do {						\
+		xstate_tests[idx].init = init_opt;	\
+		xstate_tests[idx].fill_xbuf = fill_opt;	\
+	} while (0)
+
+static struct xsave_buffer *valid_xbuf, *compared_xbuf;
+static struct xstate_test xstate_tests[XFEATURE_MAX];
+static uint32_t xstate_size;
+
+static struct xsave_buffer *alloc_xbuf(uint32_t buf_size)
+{
+	struct xsave_buffer *xbuf;
+
+	/* XSAVE buffer should be 64B-aligned. */
+	xbuf = aligned_alloc(64, buf_size);
+	if (!xbuf)
+		ksft_exit_fail_msg("aligned_alloc() failed.\n");
+
+	return xbuf;
+}
+
+static void show_test_xfeatures(void)
+{
+	uint32_t xfeature_num;
+	const char *xfeature_name;
+
+	ksft_print_msg("[NOTE] Test following xstates with mask:%lx.\n",
+		       xstate_info.mask);
+	for (xfeature_num = 0; xfeature_num < XFEATURE_MAX; xfeature_num++) {
+		if (!(xstate_info.mask & (1 << xfeature_num)))
+			continue;
+		xfeature_name = xfeature_names[xfeature_num];
+		ksft_print_msg("[NOTE] XSAVE feature num %02d: '%s'.\n",
+			       xfeature_num, xfeature_name);
+	}
+}
+
+static void compare_buf_result(struct xsave_buffer *valid_buf,
+			       struct xsave_buffer *compare_buf,
+			       const char *case_name)
+{
+	if (memcmp(&valid_buf->bytes[0], &compare_buf->bytes[0], xstate_size))
+		ksft_test_result_fail("The case: %s.\n", case_name);
+	else
+		ksft_test_result_pass("The case: %s.\n", case_name);
+}
+
+static void test_xstate_sig_handle(void)
+{
+	const char *case_name1 = "xstate around process signal handling";
+
+	ksft_print_msg("[RUN] Check xstate around signal handling test.\n");
+
+	if (xstate_sig_handle(valid_xbuf, compared_xbuf, xstate_info.mask,
+	    xstate_size)) {
+		ksft_print_msg("[NOTE] SIGUSR1 handling is done.\n");
+	} else {
+		ksft_test_result_error("%s: Didn't access SIGUSR1 handling.\n",
+				       case_name1);
+		return;
+	}
+
+	compare_buf_result(valid_xbuf, compared_xbuf, case_name1);
+}
+
+static void prepare_xstate_test(void)
+{
+	xstate_test_array_init(XFEATURE_FP, init_legacy_info,
+			       fill_fp_mxcsr_xstate_buf);
+	xstate_test_array_init(XFEATURE_SSE, init_legacy_info,
+			       fill_xmm_xstate_buf);
+	xstate_test_array_init(XFEATURE_YMM, init_ymm_info,
+			       fill_common_xstate_buf);
+	xstate_test_array_init(XFEATURE_OPMASK, init_avx512_info,
+			       fill_common_xstate_buf);
+	xstate_test_array_init(XFEATURE_ZMM_Hi256, init_avx512_info,
+			       fill_common_xstate_buf);
+	xstate_test_array_init(XFEATURE_Hi16_ZMM, init_avx512_info,
+			       fill_common_xstate_buf);
+	xstate_test_array_init(XFEATURE_PKRU, init_pkru_info,
+			       fill_pkru_xstate_buf);
+
+	xstate_tests[XSTATE_CASE_SIG].xstate_case = test_xstate_sig_handle;
+}
+
+static void test_xstate(void)
+{
+	uint32_t xfeature_num, case_num, eax, ebx, ecx, edx;
+
+	/*
+	 * CPUID.0xd.0:EBX[bit 5] enumerates the size(in bytes) required by
+	 * the XSAVE instruction for an XSAVE area containing all the user
+	 * state components corresponding to bits currently set in XCR0.
+	 */
+	__cpuid_count(CPUID_LEAF_XSTATE, CPUID_SUBLEAF_XSTATE_USER, eax, ebx,
+		      ecx, edx);
+	xstate_size = ebx;
+	valid_xbuf = alloc_xbuf(xstate_size);
+	compared_xbuf = alloc_xbuf(xstate_size);
+
+	for (xfeature_num = XFEATURE_FP; xfeature_num < XFEATURE_MAX;
+	     xfeature_num++) {
+		/* If there is no the xfeature init function, will continue. */
+		if (xstate_tests[xfeature_num].init) {
+			/* If CPU doesn't support the xfeature, will continue */
+			if (!xstate_tests[xfeature_num].init(xfeature_num))
+				continue;
+		} else {
+			continue;
+		}
+
+		/* Fill xstate buffer. */
+		if (xfeature_num != XFEATURE_PKRU) {
+			xstate_tests[xfeature_num].fill_xbuf(valid_xbuf,
+				xfeature_num, XSTATE_TESTBYTE);
+		} else {
+			/*
+			 * Bits 0-1 in first byte of PKRU must be 0 for
+			 * RW access to linear address.
+			 */
+			xstate_tests[xfeature_num].fill_xbuf(valid_xbuf,
+				xfeature_num, PKRU_TESTBYTE);
+		}
+	}
+
+	/*
+	 * Fill xstate-component bitmap(512-519 bytes) into the beginning of
+	 * xstate header. xstate header range is 512-575 bytes.
+	 */
+	*(uint64_t *)(&valid_xbuf->header) = xstate_info.mask;
+
+	show_test_xfeatures();
+
+	for (case_num = XSTATE_CASE_SIG; case_num < XSTATE_CASE_MAX; case_num++)
+		xstate_tests[case_num].xstate_case();
+
+	free(valid_xbuf);
+	free(compared_xbuf);
+}
+
+int main(void)
+{
+	ksft_print_header();
+	ksft_set_plan(NUM_TESTS);
+
+	/* Check hardware availability for xsave at first. */
+	check_cpuid_xsave_availability();
+	prepare_xstate_test();
+	test_xstate();
+
+	ksft_exit(ksft_cnt.ksft_pass == ksft_plan);
+}
diff --git a/tools/testing/selftests/x86/xstate.h b/tools/testing/selftests/x86/xstate.h
new file mode 100644
index 000000000000..ac242786f0aa
--- /dev/null
+++ b/tools/testing/selftests/x86/xstate.h
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include "../kselftest.h"
+
+#define XSTATE_TESTBYTE		0x8f
+/* Bits 0-1 in first byte of PKRU must be 0 for RW access to linear address. */
+#define PKRU_TESTBYTE		0xfc
+/* FP xstate(0-159 bytes) offset(0) and size(160 bytes) are fixed. */
+#define FP_SIZE			160
+/* XMM xstate(160-415 bytes) offset(160 byte) and size(256 bytes) are fixed. */
+#define XMM_OFFSET		160
+#define XMM_SIZE		256
+/*
+ * xstate 416-511 bytes are reserved, XSAVE header offset 512 bytes
+ * and header size 64 bytes are fixed.
+ */
+#define XSAVE_HDR_OFFSET	512
+#define XSAVE_HDR_SIZE		64
+
+#define CPUID_LEAF1_ECX_XSAVE_MASK	(1 << 26)  /* XSAVE instructions */
+#define CPUID_LEAF1_ECX_OSXSAVE_MASK	(1 << 27) /* OSXSAVE flag */
+
+#define CPUID_LEAF7_EBX_AVX2_MASK	(1U << 5) /* AVX2 instructions */
+#define CPUID_LEAF7_EBX_AVX512F_MASK	(1U << 16) /* AVX-512 Foundation */
+
+#define CPUID_LEAF7_ECX_OSPKE_MASK	(1U << 4) /* OS Protection Keys Enable */
+
+#define CPUID_LEAF_XSTATE		0xd
+#define CPUID_SUBLEAF_XSTATE_USER	0x0
+
+/* It's from arch/x86/kernel/fpu/xstate.c. */
+static const char * const xfeature_names[] = {
+	"x87 floating point registers",
+	"SSE registers",
+	"AVX registers",
+	"MPX bounds registers",
+	"MPX CSR",
+	"AVX-512 opmask",
+	"AVX-512 Hi256",
+	"AVX-512 ZMM_Hi256",
+	"Processor Trace (unused)",
+	"Protection Keys User registers",
+	"PASID state",
+	"unknown xstate feature",
+	"unknown xstate feature",
+	"unknown xstate feature",
+	"unknown xstate feature",
+	"unknown xstate feature",
+	"unknown xstate feature",
+	"AMX Tile config",
+	"AMX Tile data",
+	"unknown xstate feature",
+};
+
+/* List of XSAVE features Linux knows about. */
+enum xfeature {
+	XFEATURE_FP,
+	XFEATURE_SSE,
+	/*
+	 * Values above here are "legacy states".
+	 * Those below are "extended states".
+	 */
+	XFEATURE_YMM,
+	XFEATURE_BNDREGS,
+	XFEATURE_BNDCSR,
+	XFEATURE_OPMASK,
+	XFEATURE_ZMM_Hi256,
+	XFEATURE_Hi16_ZMM,
+	XFEATURE_PT_UNIMPLEMENTED_SO_FAR,
+	XFEATURE_PKRU,
+	XFEATURE_PASID,
+	XFEATURE_RSRVD_COMP_11,
+	XFEATURE_RSRVD_COMP_12,
+	XFEATURE_RSRVD_COMP_13,
+	XFEATURE_RSRVD_COMP_14,
+	XFEATURE_LBR,
+	XFEATURE_RSRVD_COMP_16,
+	XFEATURE_XTILE_CFG,
+	XFEATURE_XTILE_DATA,
+	XFEATURE_MAX,
+};
+
+enum xstate_case {
+	XSTATE_CASE_SIG,
+	XSTATE_CASE_MAX,
+};
+
+struct xsave_buffer {
+	union {
+		struct {
+			char legacy[XSAVE_HDR_OFFSET];
+			char header[XSAVE_HDR_SIZE];
+			char extended[0];
+		};
+		char bytes[0];
+	};
+};
+
+struct {
+	uint64_t mask;
+	uint32_t size[XFEATURE_MAX];
+	uint32_t offset[XFEATURE_MAX];
+} xstate_info;
+
+struct xstate_test {
+	bool (*init)(uint32_t xfeature_num);
+	void (*fill_xbuf)(void *buf, uint32_t xfeature_num, uint8_t test_byte);
+	void (*xstate_case)(void);
+};
+
+static void check_cpuid_xsave_availability(void)
+{
+	uint32_t eax, ebx, ecx, edx;
+
+	/*
+	 * CPUID.1:ECX.XSAVE[bit 26] enumerates general
+	 * support for the XSAVE feature set, including
+	 * XGETBV.
+	 */
+	__cpuid_count(1, 0, eax, ebx, ecx, edx);
+	if (!(ecx & CPUID_LEAF1_ECX_XSAVE_MASK))
+		ksft_exit_skip("cpuid: CPU doesn't support xsave.\n");
+
+	if (!(ecx & CPUID_LEAF1_ECX_OSXSAVE_MASK))
+		ksft_exit_skip("cpuid: CPU doesn't support OS xsave.\n");
+}
+
+/* Retrieve the mask, offset and size of a specific xstate. */
+static void retrieve_xstate_mask_size_offset(uint32_t xfeature_num)
+{
+	uint32_t eax, ebx, ecx, edx;
+
+	xstate_info.mask |= 1 << xfeature_num;
+	/*
+	 * The offset and size of xstate FP and SSE are not recorded by CPUID,
+	 * the contents of FP (x87 state) and MXCSR register are mixed in
+	 * bytes 0-511.
+	 */
+	if (xfeature_num == XFEATURE_FP || xfeature_num == XFEATURE_SSE)
+		return;
+
+	__cpuid_count(CPUID_LEAF_XSTATE, xfeature_num, eax, ebx, ecx, edx);
+	/*
+	 * CPUID.(EAX=0xd, ECX=xfeature_num), and output is as follow:
+	 * eax: xfeature num state component size
+	 * ebx: xfeature num state component offset in user buffer
+	 */
+	if (!eax || !ebx)
+		ksft_exit_fail_msg("xfeature num:%d size/offset:%d/%d is 0.\n",
+				   xfeature_num, eax, ebx);
+
+	xstate_info.size[xfeature_num] = eax;
+	xstate_info.offset[xfeature_num] = ebx;
+}
+
+/* Retrieve legacy FP and SSE xstate info. */
+static bool init_legacy_info(uint32_t xfeature_num)
+{
+	retrieve_xstate_mask_size_offset(xfeature_num);
+	return true;
+}
+
+static bool init_ymm_info(uint32_t xfeature_num)
+{
+	uint32_t eax, ebx, ecx, edx;
+
+	/* CPUID.7.0:EBX.AVX2[bit 5]: the support for AVX2 instructions. */
+	__cpuid_count(7, 0, eax, ebx, ecx, edx);
+	if (ebx & CPUID_LEAF7_EBX_AVX2_MASK) {
+		retrieve_xstate_mask_size_offset(xfeature_num);
+		return true;
+	}
+	return false;
+}
+
+static bool init_avx512_info(uint32_t xfeature_num)
+{
+	uint32_t eax, ebx, ecx, edx;
+
+	/* CPUID.7.0:EBX.AVX512F[bit 16]: support for AVX512F instructions. */
+	__cpuid_count(7, 0, eax, ebx, ecx, edx);
+	if (ebx & CPUID_LEAF7_EBX_AVX512F_MASK) {
+		retrieve_xstate_mask_size_offset(xfeature_num);
+		return true;
+	}
+	return false;
+}
+
+static bool init_pkru_info(uint32_t xfeature_num)
+{
+	uint32_t eax, ebx, ecx, edx;
+
+	/* CPUID.7.0:ECX.OSPKE[bit 4]: the support for OS set CR4.PKE. */
+	__cpuid_count(7, 0, eax, ebx, ecx, edx);
+	if (ecx & CPUID_LEAF7_ECX_OSPKE_MASK) {
+		retrieve_xstate_mask_size_offset(xfeature_num);
+		return true;
+	}
+	return false;
+}
+
+static void fill_xmm_xstate_buf(void *buf, uint32_t xfeature_num,
+				uint8_t test_byte)
+{
+	/*
+	 * Fill test byte value into SSE XMM part xstate buffer(160-415 bytes).
+	 * xstate 416-511 bytes are reserved as all 0.
+	 */
+	memset((unsigned char *)buf + XMM_OFFSET, test_byte, XMM_SIZE);
+}
+
+static void fill_common_xstate_buf(void *buf, uint32_t xfeature_num,
+				   uint8_t test_byte)
+{
+	memset((unsigned char *)buf + xstate_info.offset[xfeature_num],
+	       test_byte, xstate_info.size[xfeature_num]);
+}
+
+static void fill_pkru_xstate_buf(void *buf, uint32_t xfeature_num,
+				 uint8_t test_byte)
+{
+	/*
+	 * Only 0-3 bytes of PKRU xstate are allowed to be written. 4-7
+	 * bytes are reserved as all 0.
+	 */
+	memset((unsigned char *)buf + xstate_info.offset[XFEATURE_PKRU],
+	       test_byte, sizeof(uint32_t));
+}
diff --git a/tools/testing/selftests/x86/xstate_helpers.c b/tools/testing/selftests/x86/xstate_helpers.c
new file mode 100644
index 000000000000..a40c4b7b51b2
--- /dev/null
+++ b/tools/testing/selftests/x86/xstate_helpers.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * xstate_helpers.c - xstate helpers to prevent GCC from generating any FP code.
+ *
+ * Because xstate like XMM will not be preserved across function calls, it uses
+ * assembly instruction to call a system call of fork or raise signal, and uses
+ * the "inline" keyword in test functions in this file.
+ * To prevent GCC from generating any FP/SSE(XMM)/AVX/PKRU code, add
+ * "-mno-sse -mno-mmx -mno-sse2 -mno-avx -mno-pku" compiler arguments. stdlib.h
+ * can not be used in this test file due to GCC bug.
+ * The test functions that prepare the xstate buffers are placed in a separate
+ * xstate.c because they do not require the above requirements.
+ */
+
+#define _GNU_SOURCE
+#include <err.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+
+#include "xstate_helpers.h"
+
+/* err() exits and will not return. */
+#define fatal_error(msg, ...)	err(1, "[FAIL]\t" msg, ##__VA_ARGS__)
+/* FP and SSE are legacy xstates with fixed masks in regions 0-511 bytes. */
+#define MASK_FP_SSE	3
+
+static bool sigusr1_done;
+
+static inline void __xsave(void *xbuf, uint64_t rfbm)
+{
+	uint32_t rfbm_lo = rfbm;
+	uint32_t rfbm_hi = rfbm >> 32;
+
+	asm volatile("xsave (%%rdi)"
+		     : : "D" (xbuf), "a" (rfbm_lo), "d" (rfbm_hi)
+		     : "memory");
+}
+
+static inline void __xrstor(void *xbuf, uint64_t rfbm)
+{
+	uint32_t rfbm_lo = rfbm;
+	uint32_t rfbm_hi = rfbm >> 32;
+
+	asm volatile("xrstor (%%rdi)"
+		     : : "D" (xbuf), "a" (rfbm_lo), "d" (rfbm_hi));
+}
+
+inline void fill_fp_mxcsr_xstate_buf(void *buf, uint32_t xfeature_num,
+				     uint8_t ui8_fp)
+{
+	/*
+	 * Sets the FPU control, status, tag, instruction pointer, and
+	 * data pointer registers to their default states.
+	 */
+	asm volatile("finit");
+	/* Populate tested bytes onto FP registers stack ST0-7. */
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	asm volatile("fldl %0" : : "m" (ui8_fp));
+	/* Xsave the x87 FPU and SSE MXCSR(bytes 24-27) xstate into the buf. */
+	__xsave(buf, MASK_FP_SSE);
+}
+
+/*
+ * Because xstate like XMM, YMM registers are not preserved across function
+ * calls, so use inline function with assembly code only to raise signal.
+ */
+static inline long __raise(long pid_num, long sig_num)
+{
+	long ret, nr = SYS_kill;
+
+	asm volatile("movq %0, %%rdi" : : "r"(pid_num) : "%rdi");
+	asm volatile("movq %0, %%rsi" : : "r"(sig_num) : "%rsi");
+	asm volatile("syscall"
+		     : "=a" (ret)
+		     : "a" (nr), "b" (nr)
+		     : "rcx", "r11", "memory", "cc");
+
+	return ret;
+}
+
+static void sigusr1_handler(int signum, siginfo_t *info, void *__ctxp)
+{
+	sigusr1_done = true;
+}
+
+static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
+		       int flags)
+{
+	struct sigaction sa;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_sigaction = handler;
+	sa.sa_flags = SA_SIGINFO | flags;
+	sigemptyset(&sa.sa_mask);
+	if (sigaction(sig, &sa, 0))
+		fatal_error("sigaction");
+}
+
+static void clearhandler(int sig)
+{
+	struct sigaction sa;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = SIG_DFL;
+	sigemptyset(&sa.sa_mask);
+	if (sigaction(sig, &sa, 0))
+		fatal_error("sigaction");
+}
+
+bool xstate_sig_handle(void *valid_xbuf, void *compared_xbuf, uint64_t mask,
+		       uint32_t xstate_size)
+{
+	pid_t process_pid;
+
+	sigusr1_done = false;
+	memset(compared_xbuf, 0, xstate_size);
+	sethandler(SIGUSR1, sigusr1_handler, 0);
+	process_pid = getpid();
+	/*
+	 * Xrstor the valid buf and call syscall assembly instruction, then
+	 * save the xstate to compared buf after signal handling for comparison.
+	 */
+	__xrstor(valid_xbuf, mask);
+	__raise(process_pid, SIGUSR1);
+	__xsave(compared_xbuf, mask);
+	clearhandler(SIGUSR1);
+
+	return sigusr1_done;
+}
diff --git a/tools/testing/selftests/x86/xstate_helpers.h b/tools/testing/selftests/x86/xstate_helpers.h
new file mode 100644
index 000000000000..f50a4c1d907f
--- /dev/null
+++ b/tools/testing/selftests/x86/xstate_helpers.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdint.h>
+
+extern void fill_fp_mxcsr_xstate_buf(void *buf, uint32_t xfeature_num,
+				     uint8_t ui8_fp);
+extern bool xstate_sig_handle(void *valid_xbuf, void *compared_xbuf,
+			      uint64_t mask, uint32_t xstate_size);
