Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions kernel/inc/devices/fpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,9 @@ void unswitch_fpu(void);
/// @return 0 if fails, 1 if succeed.
int fpu_install(void);

/// @brief Check if the FPU is properly initialized.
/// @return 1 if FPU is initialized, 0 otherwise.
int fpu_is_initialized(void);

/// @}
/// @}
96 changes: 37 additions & 59 deletions kernel/src/devices/fpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,8 @@ static inline void __enable_fpu(void)
static inline void __disable_fpu(void)
{
size_t t;

__asm__ __volatile__("mov %%cr0, %0" : "=r"(t));

t |= 1U << 3U;

__asm__ __volatile__("mov %0, %%cr0" ::"r"(t));
}

Expand All @@ -73,7 +70,10 @@ static inline void __save_fpu(task_struct *proc)
}

/// Initialize the FPU.
static inline void __init_fpu(void) { __asm__ __volatile__("fninit"); }
static inline void __init_fpu(void)
{
__asm__ __volatile__("fninit");
}

/// Kernel trap for FPU usage when FPU is disabled.
/// @param f The interrupt stack frame.
Expand Down Expand Up @@ -149,7 +149,7 @@ static inline void __invalid_opcode_handler(pt_regs_t *f)
// Check if this is user mode or kernel mode
if ((f->cs & 0x3) == 0x3) {
// User mode - send SIGILL
task_struct *task = scheduler_get_current_process();
task_struct *task = scheduler_get_current_process();
// Get the action for SIGILL.
sigaction_t *action = &task->sighand.action[SIGILL - 1];
// If the user did not install a SIGILL handler, terminate immediately.
Expand All @@ -169,59 +169,15 @@ static inline void __invalid_opcode_handler(pt_regs_t *f)
}
}

/// @brief Ensure basic FPU functionality works.
/// @details
/// For processors without a FPU, this tests that maths libraries link
/// correctly. Uses a relaxed tolerance for floating point comparisons to
/// account for optimization-related precision variations in Release builds.
/// @return 1 on success, 0 on failure.
static int __fpu_test(void)
void switch_fpu(void)
{
double a = M_PI;
// First test.
for (int i = 0; i < 10000; i++) {
a = a * 1.123 + (a / 3);
a /= 1.111;
while (a > 100.0) {
a /= 3.1234563212;
}
while (a < 2.0) {
a += 1.1232132131;
}
}

// Use relaxed comparison to handle Release build precision variations
// Expected: ~50.11095685350556294679336133413
// Allow ±0.1 tolerance
double expected = 50.11095685350556294679336133413;
if ((a < (expected - 0.1)) || (a > (expected + 0.1))) {
pr_err("FPU test 1 failed: result %f not near expected %f\n", a, expected);
return 0;
}

pr_debug("FPU test 1 passed: %f\n", a);

// Second test.
a = M_PI;
for (int i = 0; i < 100; i++) {
a = a * 3 + (a / 3);
}

// Second test: just verify it's a reasonable large number
// Expected: ~60957114488184560000000000000000000000000000000000000.0
// But with precision changes in Release, just verify it's in the ballpark
if (a < 1e40) {
pr_err("FPU test 2 failed: result %e too small\n", a);
return 0;
}

pr_debug("FPU test 2 passed: %e\n", a);
return 1;
__save_fpu(scheduler_get_current_process());
}

void switch_fpu(void) { __save_fpu(scheduler_get_current_process()); }

void unswitch_fpu(void) { __restore_fpu(scheduler_get_current_process()); }
void unswitch_fpu(void)
{
__restore_fpu(scheduler_get_current_process());
}

int fpu_install(void)
{
Expand Down Expand Up @@ -260,9 +216,31 @@ int fpu_install(void)
isr_install_handler(FLOATING_POINT_ERR, &__sigfpe_handler, "floating point error");
pr_debug(" FLOATING_POINT_ERR handler installed.\n");

pr_debug("fpu_install: Running FPU test...\n");
int result = __fpu_test();
pr_debug("fpu_install: FPU test result: %d\n", result);
pr_debug("fpu_install: FPU initialization completed successfully.\n");

return 1;
}

return result;
int fpu_is_initialized(void)
{
size_t cr0, cr4;
// Read CR0
__asm__ __volatile__("mov %%cr0, %0" : "=r"(cr0));
// Read CR4
__asm__ __volatile__("mov %%cr4, %0" : "=r"(cr4));
// Check CR0: EM bit (2) should be 0, MP bit (1) should be 1
if ((cr0 & (1U << 2U)) != 0) {
return 0; // EM bit set, FPU emulation enabled
}
if ((cr0 & (1U << 1U)) == 0) {
return 0; // MP bit not set
}
// Check CR4: OSFXSR (9) and OSXMMEXCPT (10) should be set
if ((cr4 & (1U << 9U)) == 0) {
return 0; // OSFXSR not set
}
if ((cr4 & (1U << 10U)) == 0) {
return 0; // OSXMMEXCPT not set
}
return 1;
}
36 changes: 36 additions & 0 deletions kernel/src/klib/math.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,25 @@ double logx(double x, double y)
return ln(x) / ln(y);
}

double log(double x)
{
return ln(x);
}

double sin(double x)
{
double out;
__asm__ __volatile__("fldl %1; fsin" : "=t"(out) : "m"(x));
return out;
}

double cos(double x)
{
double out;
__asm__ __volatile__("fldl %1; fcos" : "=t"(out) : "m"(x));
return out;
}

/// Max power for forward and reverse projections.
#define MAXPOWTWO 4.503599627370496000E+15

Expand Down Expand Up @@ -161,3 +180,20 @@ double modf(double x, double *intpart)
// Signed fractional part.
return (x - (*intpart));
}

double macheps(double x)
{
static const double base = 2.0;
double eps;

if (isnan(x))
eps = x;
else {
eps = (x == 0.0) ? 1.0 : x;

while ((x + eps / base) != x)
eps /= base;
}

return (eps);
}
2 changes: 2 additions & 0 deletions kernel/src/tests/runner.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extern void test_buddy(void);
extern void test_page(void);
extern void test_memory_adversarial(void);
extern void test_dma(void);
extern void test_fpu(void);

/// @brief Test registry - one entry per subsystem.
static const test_entry_t test_functions[] = {
Expand All @@ -57,6 +58,7 @@ static const test_entry_t test_functions[] = {
{test_page, "Page Structure Subsystem" },
{test_dma, "DMA Zone/Allocation Tests" },
{test_memory_adversarial, "Memory Adversarial/Error Tests"},
{test_fpu, "FPU Subsystem" },
};

static const int num_tests = sizeof(test_functions) / sizeof(test_entry_t);
Expand Down
168 changes: 168 additions & 0 deletions kernel/src/tests/unit/test_fpu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/// @file test_fpu.c
/// @brief FPU unit tests.
/// @copyright (c) 2014-2024 This file is distributed under the MIT License.
/// See LICENSE.md for details.

// Setup the logging for this file (do this before any other include).
#include "sys/kernel_levels.h" // Include kernel log levels.
#define __DEBUG_HEADER__ "[TUNIT ]" ///< Change header.
#define __DEBUG_LEVEL__ LOGLEVEL_NOTICE ///< Set log level.
#include "io/debug.h" // Include debugging functions.

#include "devices/fpu.h"
#include "math.h"
#include "tests/test.h"
#include "tests/test_utils.h"

/// @brief Check if two floating point numbers are equal within a tolerance.
/// @param a First value.
/// @param b Second value.
/// @param epsilon Tolerance for comparison.
/// @return 1 if |a - b| <= epsilon, 0 otherwise.
static inline int check_float_equality(double a, double b, double epsilon)
{
return (fabs(a - b) <= epsilon) ? 1 : 0;
}

/// @brief Test that FPU is initialized.
TEST(fpu_initialized)
{
TEST_SECTION_START("FPU initialization check");
// Check that FPU is properly initialized after boot
ASSERT(fpu_is_initialized() == 1);
TEST_SECTION_END();
}

/// @brief Test basic floating point addition.
TEST(fpu_addition)
{
TEST_SECTION_START("FPU addition");
double a = 3.14;
double b = 2.86;
double result = a + b;
ASSERT(check_float_equality(result, 6.0, macheps(6.0)));
TEST_SECTION_END();
}

/// @brief Test basic floating point multiplication.
TEST(fpu_multiplication)
{
TEST_SECTION_START("FPU multiplication");
double a = 3.0;
double b = 4.0;
double result = a * b;
ASSERT(check_float_equality(result, 12.0, macheps(12.0)));
TEST_SECTION_END();
}

/// @brief Test floating point division.
TEST(fpu_division)
{
TEST_SECTION_START("FPU division");
double a = 10.0;
double b = 2.0;
double result = a / b;
ASSERT(check_float_equality(result, 5.0, macheps(5.0)));
TEST_SECTION_END();
}

/// @brief Test floating point square root.
TEST(fpu_sqrt)
{
TEST_SECTION_START("FPU square root");
double a = 9.0;
double result = sqrt(a);
ASSERT(check_float_equality(result, 3.0, macheps(3.0)));
TEST_SECTION_END();
}

/// @brief Test floating point sine function.
TEST(fpu_sin)
{
TEST_SECTION_START("FPU sine");
double a = 0.0;
double result = sin(a);
ASSERT(check_float_equality(result, 0.0, macheps(0.0)));
TEST_SECTION_END();
}

/// @brief Test floating point cosine function.
TEST(fpu_cos)
{
TEST_SECTION_START("FPU cosine");
double a = 0.0;
double result = cos(a);
ASSERT(check_float_equality(result, 1.0, macheps(1.0)));
TEST_SECTION_END();
}

/// @brief Test floating point power function.
TEST(fpu_pow)
{
TEST_SECTION_START("FPU power");
double a = 2.0;
double b = 3.0;
double result = pow(a, b);
ASSERT(check_float_equality(result, 8.0, macheps(8.0)));
TEST_SECTION_END();
}

/// @brief Test floating point logarithm.
TEST(fpu_log)
{
TEST_SECTION_START("FPU logarithm");
double a = 1.0;
double result = log(a);
ASSERT(check_float_equality(result, 0.0, macheps(0.0)));
TEST_SECTION_END();
}

/// @brief Test floating point exponential.
TEST(fpu_exp)
{
TEST_SECTION_START("FPU exponential");
double a = 0.0;
double result = exp(a);
ASSERT(check_float_equality(result, 1.0, macheps(1.0)));
TEST_SECTION_END();
}

/// @brief Test floating point precision with PI.
TEST(fpu_pi_precision)
{
TEST_SECTION_START("FPU PI precision");
double pi = M_PI;
// Check that PI is approximately 3.14159
ASSERT(pi > 3.141 && pi < 3.142);
TEST_SECTION_END();
}

/// @brief Test floating point loop accumulation.
TEST(fpu_loop_accumulation)
{
TEST_SECTION_START("FPU loop accumulation");
double sum = 0.0;
for (int i = 1; i <= 10; i++) {
sum += 1.0 / i;
}
// Harmonic series H_10 ≈ 2.9289682539682538
ASSERT(sum > 2.9 && sum < 2.95);
TEST_SECTION_END();
}

/// @brief Run all FPU tests.
void test_fpu(void)
{
test_fpu_initialized();
test_fpu_addition();
test_fpu_multiplication();
test_fpu_division();
test_fpu_sqrt();
test_fpu_sin();
test_fpu_cos();
test_fpu_pow();
test_fpu_log();
test_fpu_exp();
test_fpu_pi_precision();
test_fpu_loop_accumulation();
}
Loading