184 lines
4.9 KiB
C
184 lines
4.9 KiB
C
/// @file fpu.c
|
|
/// @brief Floating Point Unit (FPU).
|
|
/// @copyright (c) 2014-2022 This file is distributed under the MIT License.
|
|
/// See LICENSE.md for details.
|
|
|
|
// Include the kernel log levels.
|
|
#include "sys/kernel_levels.h"
|
|
/// Change the header.
|
|
#define __DEBUG_HEADER__ "[FPU ]"
|
|
/// Set the log level.
|
|
#define __DEBUG_LEVEL__ LOGLEVEL_NOTICE
|
|
|
|
#include "devices/fpu.h"
|
|
#include "descriptor_tables/isr.h"
|
|
#include "io/debug.h"
|
|
#include "string.h"
|
|
#include "assert.h"
|
|
#include "process/scheduler.h"
|
|
#include "math.h"
|
|
#include "process/process.h"
|
|
#include "system/signal.h"
|
|
|
|
/// Pointerst to the current thread using the FPU.
|
|
task_struct *thread_using_fpu = NULL;
|
|
/// Temporary aligned buffer for copying around FPU contexts.
|
|
uint8_t saves[512] __attribute__((aligned(16)));
|
|
|
|
/// @brief Set the FPU control word.
|
|
/// @param cw What to set the control word to.
|
|
static inline void __set_fpu_cw(const uint16_t cw)
|
|
{
|
|
__asm__ __volatile__("fldcw %0" ::"m"(cw));
|
|
}
|
|
|
|
/// @brief Enable the FPU and SSE.
|
|
static inline void __enable_fpu()
|
|
{
|
|
__asm__ __volatile__("clts");
|
|
size_t t;
|
|
__asm__ __volatile__("mov %%cr0, %0"
|
|
: "=r"(t));
|
|
t &= ~(1U << 2U);
|
|
t |= (1U << 1U);
|
|
__asm__ __volatile__("mov %0, %%cr0" ::"r"(t));
|
|
__asm__ __volatile__("mov %%cr4, %0"
|
|
: "=r"(t));
|
|
t |= 3U << 9U;
|
|
__asm__ __volatile__("mov %0, %%cr4" ::"r"(t));
|
|
}
|
|
|
|
/// Disable FPU and SSE so it traps to the kernel.
|
|
static inline void __disable_fpu()
|
|
{
|
|
size_t t;
|
|
|
|
__asm__ __volatile__("mov %%cr0, %0"
|
|
: "=r"(t));
|
|
|
|
t |= 1U << 3U;
|
|
|
|
__asm__ __volatile__("mov %0, %%cr0" ::"r"(t));
|
|
}
|
|
|
|
/// @brief Restore the FPU for a process.
|
|
static inline void __restore_fpu(task_struct *proc)
|
|
{
|
|
assert(proc && "Trying to restore FPU of NULL process.");
|
|
|
|
memcpy(&saves, (uint8_t *)&proc->thread.fpu_register, 512);
|
|
|
|
__asm__ __volatile__("fxrstor (%0)" ::"r"(saves));
|
|
}
|
|
|
|
/// Save the FPU for a process.
|
|
static inline void __save_fpu(task_struct *proc)
|
|
{
|
|
assert(proc && "Trying to save FPU of NULL process.");
|
|
|
|
__asm__ __volatile__("fxsave (%0)" ::"r"(saves));
|
|
|
|
memcpy((uint8_t *)&proc->thread.fpu_register, &saves, 512);
|
|
}
|
|
|
|
/// Initialize the FPU.
|
|
static inline void __init_fpu()
|
|
{
|
|
__asm__ __volatile__("fninit");
|
|
}
|
|
|
|
/// Kernel trap for FPU usage when FPU is disabled.
|
|
/// @param f The interrupt stack frame.
|
|
static inline void __invalid_op(pt_regs *f)
|
|
{
|
|
pr_debug("__invalid_op(%p)\n", f);
|
|
// First, turn the FPU on.
|
|
__enable_fpu();
|
|
if (thread_using_fpu == scheduler_get_current_process()) {
|
|
// If this is the thread that last used the FPU, do nothing.
|
|
return;
|
|
}
|
|
if (thread_using_fpu) {
|
|
// If there is a thread that was using the FPU, save its state.
|
|
__save_fpu(thread_using_fpu);
|
|
}
|
|
thread_using_fpu = scheduler_get_current_process();
|
|
if (!thread_using_fpu->thread.fpu_enabled) {
|
|
/*
|
|
* If the FPU has not been used in this thread previously,
|
|
* we need to initialize it.
|
|
*/
|
|
__init_fpu();
|
|
thread_using_fpu->thread.fpu_enabled = true;
|
|
return;
|
|
}
|
|
// Otherwise we restore the context for this thread.
|
|
__restore_fpu(thread_using_fpu);
|
|
}
|
|
|
|
/// Kernel trap for various integer and floating-point errors
|
|
/// @param f The interrupt stack frame.
|
|
static inline void __sigfpe_handler(pt_regs* f)
|
|
{
|
|
pr_debug("__sigfpe_handler(%p)\n", f);
|
|
|
|
// Notifies current process
|
|
thread_using_fpu = scheduler_get_current_process();
|
|
sys_kill(thread_using_fpu->pid, SIGFPE);
|
|
}
|
|
|
|
|
|
/// @brief Ensure basic FPU functionality works.
|
|
/// @details
|
|
/// For processors without a FPU, this tests that maths libraries link
|
|
/// correctly.
|
|
static int __fpu_test()
|
|
{
|
|
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;
|
|
}
|
|
if (a != 50.11095685350556294679336133413)
|
|
return 0;
|
|
// Second test.
|
|
a = M_PI;
|
|
for (int i = 0; i < 100; i++)
|
|
a = a * 3 + (a / 3);
|
|
return (a == 60957114488184560000000000000000000000000000000000000.0);
|
|
}
|
|
|
|
void switch_fpu()
|
|
{
|
|
__save_fpu(scheduler_get_current_process());
|
|
}
|
|
|
|
void unswitch_fpu()
|
|
{
|
|
__restore_fpu(scheduler_get_current_process());
|
|
}
|
|
|
|
int fpu_install()
|
|
{
|
|
__enable_fpu();
|
|
__init_fpu();
|
|
__save_fpu(scheduler_get_current_process());
|
|
|
|
// Install the handler for device missing
|
|
isr_install_handler(DEV_NOT_AVL, &__invalid_op, "fpu: device missing");
|
|
|
|
// Install handlers for floating points and integers errors
|
|
isr_install_handler(DIVIDE_ERROR, &__sigfpe_handler, "divide error");
|
|
|
|
// NB: The exceptions bolow don't seems to ever trigger
|
|
//isr_install_handler(OVERFLOW, &__sigfpe_handler, "overflow");
|
|
//isr_install_handler(FLOATING_POINT_ERR, &__sigfpe_handler, "floating point error");
|
|
|
|
return __fpu_test();
|
|
}
|