18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Test cases for using floating point operations inside a kernel module. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * This tests kernel_fpu_begin() and kernel_fpu_end() functions, especially 68c2ecf20Sopenharmony_ci * when userland has modified the floating point control registers. The kernel 78c2ecf20Sopenharmony_ci * state might depend on the state set by the userland thread that was active 88c2ecf20Sopenharmony_ci * before a syscall. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * To facilitate the test, this module registers file 118c2ecf20Sopenharmony_ci * /sys/kernel/debug/selftest_helpers/test_fpu, which when read causes a 128c2ecf20Sopenharmony_ci * sequence of floating point operations. If the operations fail, either the 138c2ecf20Sopenharmony_ci * read returns error status or the kernel crashes. 148c2ecf20Sopenharmony_ci * If the operations succeed, the read returns "1\n". 158c2ecf20Sopenharmony_ci */ 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 208c2ecf20Sopenharmony_ci#include <asm/fpu/api.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic int test_fpu(void) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci /* 258c2ecf20Sopenharmony_ci * This sequence of operations tests that rounding mode is 268c2ecf20Sopenharmony_ci * to nearest and that denormal numbers are supported. 278c2ecf20Sopenharmony_ci * Volatile variables are used to avoid compiler optimizing 288c2ecf20Sopenharmony_ci * the calculations away. 298c2ecf20Sopenharmony_ci */ 308c2ecf20Sopenharmony_ci volatile double a, b, c, d, e, f, g; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci a = 4.0; 338c2ecf20Sopenharmony_ci b = 1e-15; 348c2ecf20Sopenharmony_ci c = 1e-310; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci /* Sets precision flag */ 378c2ecf20Sopenharmony_ci d = a + b; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* Result depends on rounding mode */ 408c2ecf20Sopenharmony_ci e = a + b / 2; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci /* Denormal and very large values */ 438c2ecf20Sopenharmony_ci f = b / c; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* Depends on denormal support */ 468c2ecf20Sopenharmony_ci g = a + c * f; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci if (d > a && e > a && g > a) 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci else 518c2ecf20Sopenharmony_ci return -EINVAL; 528c2ecf20Sopenharmony_ci} 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic int test_fpu_get(void *data, u64 *val) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci int status = -EINVAL; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci kernel_fpu_begin(); 598c2ecf20Sopenharmony_ci status = test_fpu(); 608c2ecf20Sopenharmony_ci kernel_fpu_end(); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci *val = 1; 638c2ecf20Sopenharmony_ci return status; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ciDEFINE_SIMPLE_ATTRIBUTE(test_fpu_fops, test_fpu_get, NULL, "%lld\n"); 678c2ecf20Sopenharmony_cistatic struct dentry *selftest_dir; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int __init test_fpu_init(void) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci selftest_dir = debugfs_create_dir("selftest_helpers", NULL); 728c2ecf20Sopenharmony_ci if (!selftest_dir) 738c2ecf20Sopenharmony_ci return -ENOMEM; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci debugfs_create_file("test_fpu", 0444, selftest_dir, NULL, 768c2ecf20Sopenharmony_ci &test_fpu_fops); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci return 0; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic void __exit test_fpu_exit(void) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci debugfs_remove(selftest_dir); 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cimodule_init(test_fpu_init); 878c2ecf20Sopenharmony_cimodule_exit(test_fpu_exit); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 90