// SPDX-License-Identifier: GPL-2.0+
/* This testcase operates with the test_fpu kernel driver.
 * It modifies the FPU control register in user mode and calls the kernel
 * module to perform floating point operations in the kernel. The control
 * register value should be independent between kernel and user mode.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fenv.h>
#include <unistd.h>
#include <fcntl.h>

const char *test_fpu_path = "/sys/kernel/debug/selftest_helpers/test_fpu";

int main(void)
{
	char dummy[1];
	int fd = open(test_fpu_path, O_RDONLY);

	if (fd < 0) {
		printf("[SKIP]\tcan't access %s: %s\n",
		       test_fpu_path, strerror(errno));
		return 0;
	}

	if (read(fd, dummy, 1) < 0) {
		printf("[FAIL]\taccess with default rounding mode failed\n");
		return 1;
	}

	fesetround(FE_DOWNWARD);
	if (read(fd, dummy, 1) < 0) {
		printf("[FAIL]\taccess with downward rounding mode failed\n");
		return 2;
	}
	if (fegetround() != FE_DOWNWARD) {
		printf("[FAIL]\tusermode rounding mode clobbered\n");
		return 3;
	}

	/* Note: the tests up to this point are quite safe and will only return
	 * an error. But the exception mask setting can cause misbehaving kernel
	 * to crash.
	 */
	feclearexcept(FE_ALL_EXCEPT);
	feenableexcept(FE_ALL_EXCEPT);
	if (read(fd, dummy, 1) < 0) {
		printf("[FAIL]\taccess with fpu exceptions unmasked failed\n");
		return 4;
	}
	if (fegetexcept() != FE_ALL_EXCEPT) {
		printf("[FAIL]\tusermode fpu exception mask clobbered\n");
		return 5;
	}

	printf("[OK]\ttest_fpu\n");
	return 0;
}