summaryrefslogtreecommitdiffstats
path: root/lib/test_fpu.c
diff options
context:
space:
mode:
authorPetteri Aimonen <jpa@git.mail.kapsi.fi>2020-06-18 16:37:37 +0200
committerBorislav Petkov <bp@suse.de>2020-06-29 10:02:23 +0200
commit4185b3b92792eaec5869266e594338343421ffb0 (patch)
treeac708daf001002b2058ff899c74462500684e85c /lib/test_fpu.c
parentx86/fpu: Reset MXCSR to default in kernel_fpu_begin() (diff)
downloadlinux-4185b3b92792eaec5869266e594338343421ffb0.tar.xz
linux-4185b3b92792eaec5869266e594338343421ffb0.zip
selftests/fpu: Add an FPU selftest
Add a selftest for the usage of FPU code in kernel mode. Currently only implemented for x86. In the future, kernel FPU testing could be unified between the different architectures supporting it. [ bp: - Split out from a conglomerate patch, put comments over statements. - run the test only on debugfs write. - Add bare-minimum run_test_fpu.sh, run 1000 iterations on all CPUs by default. - Add conditionally -msse2 so that clang doesn't generate library calls. - Use cc-option to detect gcc 7.1 not supporting -mpreferred-stack-boundary=3 (amluto). - Document stuff so that we don't forget. - Fix: ld: lib/test_fpu.o: in function `test_fpu_get': >> test_fpu.c:(.text+0x16e): undefined reference to `__sanitizer_cov_trace_cmpd' >> ld: test_fpu.c:(.text+0x1a7): undefined reference to `__sanitizer_cov_trace_cmpd' ld: test_fpu.c:(.text+0x1e0): undefined reference to `__sanitizer_cov_trace_cmpd' ] Reported-by: kernel test robot <lkp@intel.com> Signed-off-by: Petteri Aimonen <jpa@git.mail.kapsi.fi> Signed-off-by: Borislav Petkov <bp@suse.de> Reviewed-by: Nick Desaulniers <ndesaulniers@google.com> Link: https://lkml.kernel.org/r/20200624114646.28953-3-bp@alien8.de
Diffstat (limited to 'lib/test_fpu.c')
-rw-r--r--lib/test_fpu.c89
1 files changed, 89 insertions, 0 deletions
diff --git a/lib/test_fpu.c b/lib/test_fpu.c
new file mode 100644
index 000000000000..c33764aa3eb8
--- /dev/null
+++ b/lib/test_fpu.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test cases for using floating point operations inside a kernel module.
+ *
+ * This tests kernel_fpu_begin() and kernel_fpu_end() functions, especially
+ * when userland has modified the floating point control registers. The kernel
+ * state might depend on the state set by the userland thread that was active
+ * before a syscall.
+ *
+ * To facilitate the test, this module registers file
+ * /sys/kernel/debug/selftest_helpers/test_fpu, which when read causes a
+ * sequence of floating point operations. If the operations fail, either the
+ * read returns error status or the kernel crashes.
+ * If the operations succeed, the read returns "1\n".
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <asm/fpu/api.h>
+
+static int test_fpu(void)
+{
+ /*
+ * This sequence of operations tests that rounding mode is
+ * to nearest and that denormal numbers are supported.
+ * Volatile variables are used to avoid compiler optimizing
+ * the calculations away.
+ */
+ volatile double a, b, c, d, e, f, g;
+
+ a = 4.0;
+ b = 1e-15;
+ c = 1e-310;
+
+ /* Sets precision flag */
+ d = a + b;
+
+ /* Result depends on rounding mode */
+ e = a + b / 2;
+
+ /* Denormal and very large values */
+ f = b / c;
+
+ /* Depends on denormal support */
+ g = a + c * f;
+
+ if (d > a && e > a && g > a)
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static int test_fpu_get(void *data, u64 *val)
+{
+ int status = -EINVAL;
+
+ kernel_fpu_begin();
+ status = test_fpu();
+ kernel_fpu_end();
+
+ *val = 1;
+ return status;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(test_fpu_fops, test_fpu_get, NULL, "%lld\n");
+static struct dentry *selftest_dir;
+
+static int __init test_fpu_init(void)
+{
+ selftest_dir = debugfs_create_dir("selftest_helpers", NULL);
+ if (!selftest_dir)
+ return -ENOMEM;
+
+ debugfs_create_file("test_fpu", 0444, selftest_dir, NULL,
+ &test_fpu_fops);
+
+ return 0;
+}
+
+static void __exit test_fpu_exit(void)
+{
+ debugfs_remove(selftest_dir);
+}
+
+module_init(test_fpu_init);
+module_exit(test_fpu_exit);
+
+MODULE_LICENSE("GPL");