diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index be94833d390a..e642e4b8a726 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2223,6 +2223,7 @@ extern const struct bpf_func_proto bpf_find_vma_proto;
 extern const struct bpf_func_proto bpf_loop_proto;
 extern const struct bpf_func_proto bpf_strncmp_proto;
 extern const struct bpf_func_proto bpf_copy_from_user_task_proto;
+extern const struct bpf_func_proto bpf_get_reg_val_proto;
 
 const struct bpf_func_proto *tracing_prog_func_proto(
   enum bpf_func_id func_id, const struct bpf_prog *prog);
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 0de7d6b3af5b..cb81142a751a 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -1300,7 +1300,7 @@ BPF_CALL_5(get_reg_val, void *, dst, u32, size,
 BTF_ID_LIST(bpf_get_reg_val_ids)
 BTF_ID(struct, pt_regs)
 
-static const struct bpf_func_proto bpf_get_reg_val_proto = {
+const struct bpf_func_proto bpf_get_reg_val_proto = {
 	.func	= get_reg_val,
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_UNINIT_MEM,
diff --git a/net/bpf/bpf_dummy_struct_ops.c b/net/bpf/bpf_dummy_struct_ops.c
index d0e54e30658a..1f3933cd8aa6 100644
--- a/net/bpf/bpf_dummy_struct_ops.c
+++ b/net/bpf/bpf_dummy_struct_ops.c
@@ -171,7 +171,20 @@ static int bpf_dummy_ops_btf_struct_access(struct bpf_verifier_log *log,
 	return atype == BPF_READ ? err : NOT_INIT;
 }
 
+static const struct bpf_func_proto *
+bpf_dummy_ops_get_func_proto(enum bpf_func_id func_id,
+			     const struct bpf_prog *prog)
+{
+	switch (func_id) {
+	case BPF_FUNC_get_reg_val:
+		return &bpf_get_reg_val_proto;
+	default:
+		return bpf_base_func_proto(func_id);
+	}
+}
+
 static const struct bpf_verifier_ops bpf_dummy_verifier_ops = {
+	.get_func_proto = bpf_dummy_ops_get_func_proto,
 	.is_valid_access = bpf_dummy_ops_is_valid_access,
 	.btf_struct_access = bpf_dummy_ops_btf_struct_access,
 };
diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
index e585e1cefc77..b2b35138b097 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright (c) 2020 Facebook */
+#include <asm/fpu/api.h>
 #include <linux/btf.h>
 #include <linux/btf_ids.h>
 #include <linux/error-injection.h>
@@ -25,6 +26,13 @@ bpf_testmod_test_mod_kfunc(int i)
 	*(int *)this_cpu_ptr(&bpf_testmod_ksym_percpu) = i;
 }
 
+noinline void
+bpf_testmod_save_fpregs(void)
+{
+	kernel_fpu_begin();
+	kernel_fpu_end();
+}
+
 struct bpf_testmod_btf_type_tag_1 {
 	int a;
 };
@@ -150,6 +158,7 @@ static struct bin_attribute bin_attr_bpf_testmod_file __ro_after_init = {
 
 BTF_SET_START(bpf_testmod_check_kfunc_ids)
 BTF_ID(func, bpf_testmod_test_mod_kfunc)
+BTF_ID(func, bpf_testmod_save_fpregs)
 BTF_SET_END(bpf_testmod_check_kfunc_ids)
 
 static const struct btf_kfunc_id_set bpf_testmod_kfunc_set = {
@@ -166,6 +175,10 @@ static int bpf_testmod_init(void)
 	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_testmod_kfunc_set);
 	if (ret < 0)
 		return ret;
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &bpf_testmod_kfunc_set);
+	if (ret < 0)
+		return ret;
+
 	if (bpf_fentry_test1(0) < 0)
 		return -EINVAL;
 	return sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
diff --git a/tools/testing/selftests/bpf/prog_tests/usdt.c b/tools/testing/selftests/bpf/prog_tests/usdt.c
index f98749ac74a7..3866cb004b22 100644
--- a/tools/testing/selftests/bpf/prog_tests/usdt.c
+++ b/tools/testing/selftests/bpf/prog_tests/usdt.c
@@ -8,6 +8,11 @@
 #include "test_usdt.skel.h"
 #include "test_urandom_usdt.skel.h"
 
+/* Need to keep consistent with definition in include/linux/bpf.h */
+struct bpf_dummy_ops_state {
+	int val;
+};
+
 int lets_test_this(int);
 
 static volatile int idx = 2;
@@ -415,6 +420,41 @@ static void subtest_urandom_usdt(bool auto_attach)
 	test_urandom_usdt__destroy(skel);
 }
 
+static void subtest_reg_val_fpustate(void)
+{
+	struct bpf_dummy_ops_state in_state;
+	struct test_urandom_usdt__bss *bss;
+	struct test_urandom_usdt *skel;
+	u64 in_args[1];
+	u64 regval[2];
+	int err, fd;
+
+	in_state.val = 0; /* unused */
+	in_args[0] = (unsigned long)&in_state;
+
+	LIBBPF_OPTS(bpf_test_run_opts, attr,
+		   .ctx_in = in_args,
+		   .ctx_size_in = sizeof(in_args),
+	);
+
+	skel = test_urandom_usdt__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open"))
+		return;
+	bss = skel->bss;
+
+	fd = bpf_program__fd(skel->progs.save_fpregs_and_read);
+	regval[0] = 42;
+	regval[1] = 0;
+	asm("movdqa %0, %%xmm10" : "=m"(*(char *)regval));
+
+	err = bpf_prog_test_run_opts(fd, &attr);
+	ASSERT_OK(err, "save_fpregs_and_read");
+	ASSERT_EQ(bss->fpregs_dummy_opts_xmm_val, 42, "fpregs_dummy_opts_xmm_val");
+
+	close(fd);
+	test_urandom_usdt__destroy(skel);
+}
+
 void test_usdt(void)
 {
 	if (test__start_subtest("basic"))
@@ -425,4 +465,6 @@ void test_usdt(void)
 		subtest_urandom_usdt(true /* auto_attach */);
 	if (test__start_subtest("urand_pid_attach"))
 		subtest_urandom_usdt(false /* auto_attach */);
+	if (test__start_subtest("bpf_get_reg_val_fpustate"))
+		subtest_reg_val_fpustate();
 }
diff --git a/tools/testing/selftests/bpf/progs/test_urandom_usdt.c b/tools/testing/selftests/bpf/progs/test_urandom_usdt.c
index 575761863eb6..2c8b6709606a 100644
--- a/tools/testing/selftests/bpf/progs/test_urandom_usdt.c
+++ b/tools/testing/selftests/bpf/progs/test_urandom_usdt.c
@@ -67,6 +67,30 @@ int BPF_USDT(urandlib_read_with_sema, int iter_num, int iter_cnt, int buf_sz)
 	return 0;
 }
 
+extern void bpf_testmod_save_fpregs(void) __ksym;
+
+u64 fpregs_dummy_opts_xmm_val;
+
+SEC("struct_ops/save_fpregs_and_read")
+int BPF_PROG(save_fpregs_and_read, struct bpf_dummy_ops_state *unused)
+{
+	struct task_struct *tsk;
+	u64 val[2];
+
+	bpf_testmod_save_fpregs();
+	tsk = bpf_get_current_task_btf();
+
+	bpf_get_reg_val(&val[0], 16, (u64)BPF_GETREG_X86_XMM10 << 32, NULL, tsk);
+	__sync_fetch_and_add(&fpregs_dummy_opts_xmm_val, val[0]);
+
+	return 0;
+}
+
+SEC(".struct_ops")
+struct bpf_dummy_ops dummy_ops = {
+	.test_1 = (void *)save_fpregs_and_read,
+};
+
 int urandlib_xmm_reg_read_buf_sz_sum;
 SEC("usdt/./liburandom_read_xmm.so:urandlib:xmm_reg_read")
 int BPF_USDT(urandlib_xmm_reg_read, int *f1, int *f2, int *f3, int a, int b,
diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c
index a07da648af3b..27a3e8cb9c36 100644
--- a/tools/testing/selftests/bpf/test_progs.c
+++ b/tools/testing/selftests/bpf/test_progs.c
@@ -620,6 +620,9 @@ int kern_sync_rcu(void)
 
 static void unload_bpf_testmod(void)
 {
+	bool tried_again = false;
+
+again:
 	if (kern_sync_rcu())
 		fprintf(env.stderr, "Failed to trigger kernel-side RCU sync!\n");
 	if (delete_module("bpf_testmod", 0)) {
@@ -627,6 +630,10 @@ static void unload_bpf_testmod(void)
 			if (verbose())
 				fprintf(stdout, "bpf_testmod.ko is already unloaded.\n");
 			return;
+		} else if (errno == EAGAIN && !tried_again) {
+			tried_again = true;
+			usleep(20 * 1000);
+			goto again;
 		}
 		fprintf(env.stderr, "Failed to unload bpf_testmod.ko from kernel: %d\n", -errno);
 		return;
