zephyr/arch/x86/core/float.c

430 lines
14 KiB
C

/*
* Copyright (c) 2010-2014 Wind River Systems, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* @brief Floating point resource sharing routines
*
* This module allows multiple tasks and fibers to safely share the system's
* floating point resources, by allowing the system to save FPU state
* information in a task or fiber's stack region when a pre-emptive context
* switch occurs.
*
* The floating point resource sharing mechanism is designed for minimal
* intrusiveness. Floating point thread saving is only performed for tasks and
* fibers that explicitly enable FP resource sharing, to avoid impacting the
* stack size requirements of all other tasks and fibers. For those tasks and
* fibers that do require FP resource sharing, a "lazy save/restore" mechanism
* is employed so that the FPU's register sets are only switched in and out
* when absolutely necessary; this avoids wasting effort preserving them when
* there is no risk that they will be altered, or when there is no need to
* preserve their contents.
*
* The following APIs are provided to allow floating point resource sharing to
* be enabled or disabled at run-time:
*
* void fiber_float_enable (nano_thread_id_t thread_id, unsigned int options)
* void task_float_enable (nano_thread_id_t thread_id, unsigned int options)
* void fiber_float_disable (nano_thread_id_t thread_id)
* void task_float_disable (nano_thread_id_t thread_id)
*
* The 'options' parameter is used to specify what non-integer capabilities are
* being used. The same options accepted by fiber_fiber_start() are used in the
* aforementioned APIs, namely USE_FP and USE_SSE.
*
* If the nanokernel has been built without SSE instruction support
* (CONFIG_SSE), the system treats USE_SSE as if it was USE_FP.
*
* If the nanokernel has been built without floating point resource sharing
* support (CONFIG_FP_SHARING), the aforementioned APIs and capabilities do not
* exist.
*
* NOTE
* It is possible for a single task or fiber to utilize floating instructions
* _without_ enabling the FP resource sharing feature. Since no other task or
* fiber uses the FPU the FP registers won't change when the FP-capable task or
* fiber isn't executing, meaning there is no need to save the registers.
*
* WARNING
* The use of floating point instructions by ISRs is not supported by the
* kernel.
*
* INTERNAL
* If automatic enabling of floating point resource sharing _is not_ configured
* the system leaves CR0[TS] = 0 for all tasks and fibers. This means that any
* task or fiber can perform floating point operations at any time without
* causing an exception, and the system won't stop a task or fiber that
* shouldn't be doing FP stuff from doing it.
*
* If automatic enabling of floating point resource sharing _is_ configured
* the system leaves CR0[TS] = 0 only for tasks and fibers that are allowed to
* perform FP operations. All other tasks and fibers have CR0[TS] = 1 so that
* an attempt to perform an FP operation will cause an exception, allowing the
* system to enable FP resource sharing on its behalf.
*/
#ifdef CONFIG_MICROKERNEL
#include <microkernel.h>
#include <micro_private_types.h>
#endif /* CONFIG_MICROKERNEL */
#include <nano_private.h>
#include <toolchain.h>
#include <asm_inline.h>
/* the entire library vanishes without the FP_SHARING option enabled */
#ifdef CONFIG_FP_SHARING
#if defined(CONFIG_SSE)
extern uint32_t _sse_mxcsr_default_value; /* SSE control/status register default value */
#endif /* CONFIG_SSE */
/**
*
* @brief Save non-integer context information
*
* This routine saves the system's "live" non-integer context into the
* specified TCS. If the specified task or fiber supports SSE then
* x87/MMX/SSEx thread info is saved, otherwise only x87/MMX thread is saved.
*
* @param tcs TBD
*
* @return N/A
*/
static void _FpCtxSave(struct tcs *tcs)
{
_do_fp_ctx_save(tcs->flags & USE_SSE, &tcs->preempFloatReg);
}
/**
*
* @brief Initialize non-integer context information
*
* This routine initializes the system's "live" non-integer context.
*
* @param tcs TBD
*
* @return N/A
*/
static inline void _FpCtxInit(struct tcs *tcs)
{
_do_fp_ctx_init(tcs->flags & USE_SSE);
}
/**
*
* @brief Enable preservation of non-integer context information
*
* This routine allows the specified task/fiber (which may be the active
* task/fiber) to safely share the system's floating point registers with
* other tasks/fibers. The <options> parameter indicates which floating point
* register sets will be used by the specified task/fiber:
*
* a) USE_FP indicates x87 FPU and MMX registers only
* b) USE_SSE indicates x87 FPU and MMX and SSEx registers
*
* Invoking this routine creates a floating point thread for the task/fiber
* that corresponds to an FPU that has been reset. The system will thereafter
* protect the task/fiber's FP context so that it is not altered during
* a pre-emptive context switch.
*
* WARNING
* This routine should only be used to enable floating point support for a
* task/fiber that does not currently have such support enabled already.
*
* @param tcs TDB
* @param options set to either USE_FP or USE_SSE
*
* @return N/A
*
* INTERNAL
* Since the transition from "non-FP supporting" to "FP supporting" must be done
* atomically to avoid confusing the floating point logic used by _Swap(),
* this routine locks interrupts to ensure that a context switch does not occur,
* The locking isn't really needed when the routine is called by a fiber
* (since context switching can't occur), but it is harmless and allows a single
* routine to be called by both tasks and fibers (thus saving code space).
*
* If necessary, the interrupt latency impact of calling this routine from a
* fiber could be lessened by re-designing things so that only task-type callers
* locked interrupts (i.e. move the locking to task_float_enable()). However,
* all calls to fiber_float_enable() would need to be reviewed to ensure they
* are only used from a fiber, rather than from "generic" code used by both
* tasks and fibers.
*/
void _FpEnable(struct tcs *tcs, unsigned int options)
{
unsigned int imask;
struct tcs *fp_owner;
/* Lock interrupts to prevent a pre-emptive context switch from occuring
*/
imask = irq_lock();
/* Indicate task/fiber requires non-integer context saving */
tcs->flags |= options | USE_FP; /* USE_FP is treated as a "dirty bit" */
/*
* Current task/fiber might not allow FP instructions, so clear CR0[TS]
* so we can use them. (CR0[TS] gets restored later on, if necessary.)
*/
__asm__ volatile("clts\n\t");
/*
* Save the existing non-integer context (since it is about to change),
* but only if the FPU is "owned" by an FP-capable task that is
* currently
* handling an interrupt or exception (meaning it's FP context must be
* preserved).
*/
fp_owner = _nanokernel.current_fp;
if (fp_owner) {
if (fp_owner->flags & INT_OR_EXC_MASK) {
_FpCtxSave(fp_owner);
}
}
/* Now create a virgin FP context */
_FpCtxInit(tcs);
/* Associate the new FP context with the specified task/fiber */
if (tcs == _nanokernel.current) {
/*
* When enabling FP support for self, just claim ownership of
*the FPU
* and leave CR0[TS] unset.
*
* (Note: the FP context is "live" in hardware, not saved in TCS.)
*/
_nanokernel.current_fp = tcs;
} else {
/*
* When enabling FP support for someone else, assign ownership
* of the FPU to them (unless we need it ourselves).
*/
if ((_nanokernel.current->flags & USE_FP) != USE_FP) {
/*
* We are not FP-capable, so mark FPU as owned by the
* thread
* we've just enabled FP support for, then disable our
* own
* FP access by setting CR0[TS] to its original state.
*/
_nanokernel.current_fp = tcs;
_FpAccessDisable();
} else {
/*
* We are FP-capable (and thus had FPU ownership on
*entry), so save
* the new FP context in their TCS, leave FPU ownership
*with self,
* and leave CR0[TS] unset.
*
* Note: The saved FP context is needed in case the task
*or fiber
* we enabled FP support for is currently pre-empted,
*since _Swap()
* uses it to restore FP context when the task/fiber
*re-activates.
*
* Note: Saving the FP context reinits the FPU, and thus
*our own
* FP context, but that's OK since it didn't need to be
*preserved.
* (i.e. We aren't currently handling an interrupt or
*exception.)
*/
_FpCtxSave(tcs);
}
}
irq_unlock(imask);
}
/**
*
* @brief Enable preservation of non-integer context information
*
* This routine allows a fiber to permit a task/fiber (including itself) to
* safely share the system's floating point registers with other tasks/fibers.
*
* See the description of _FpEnable() for further details.
*
* @return N/A
*/
FUNC_ALIAS(_FpEnable, fiber_float_enable, void);
/**
*
* @brief Enable preservation of non-integer context information
*
* This routine allows a task to permit a task/fiber (including itself) to
* safely share the system's floating point registers with other tasks/fibers.
*
* See the description of _FpEnable() for further details.
*
* @return N/A
*/
FUNC_ALIAS(_FpEnable, task_float_enable, void);
/**
*
* @brief Disable preservation of non-integer context information
*
* This routine prevents the specified task/fiber (which may be the active
* task/fiber) from safely sharing any of the system's floating point registers
* with other tasks/fibers.
*
* WARNING
* This routine should only be used to disable floating point support for
* a task/fiber that currently has such support enabled.
*
* @param tcs TBD
*
* @return N/A
*
* INTERNAL
* Since the transition from "FP supporting" to "non-FP supporting" must be done
* atomically to avoid confusing the floating point logic used by _Swap(),
* this routine locks interrupts to ensure that a context switch does not occur,
* The locking isn't really needed when the routine is called by a fiber
* (since context switching can't occur), but it is harmless and allows a single
* routine to be called by both tasks and fibers (thus saving code space).
*
* If necessary, the interrupt latency impact of calling this routine from a
* fiber could be lessened by re-designing things so that only task-type callers
* locked interrupts (i.e. move the locking to task_float_disable()). However,
* all calls to fiber_float_disable() would need to be reviewed to ensure they
* are only used from a fiber, rather than from "generic" code used by both
* tasks and fibers.
*/
void _FpDisable(struct tcs *tcs)
{
unsigned int imask;
/* Lock interrupts to prevent a pre-emptive context switch from occuring
*/
imask = irq_lock();
/*
* Disable _all_ floating point capabilities for the task/fiber,
* regardless
* of the options specified at the time support was enabled.
*/
tcs->flags &= ~(USE_FP | USE_SSE);
if (tcs == _nanokernel.current) {
_FpAccessDisable();
_nanokernel.current_fp = (struct tcs *)0;
} else {
if (_nanokernel.current_fp == tcs)
_nanokernel.current_fp = (struct tcs *)0;
}
irq_unlock(imask);
}
/**
*
* @brief Disable preservation of non-integer context
*information
*
* This routine allows a fiber to disallow a task/fiber (including itself) from
* safely sharing any of the system's floating point registers with other
* tasks/fibers.
*
* WARNING
* This routine should only be used to disable floating point support for
* a task/fiber that currently has such support enabled.
*
* @return N/A
*/
FUNC_ALIAS(_FpDisable, fiber_float_disable, void);
/**
*
* @brief Disable preservation of non-integer context information
*
* This routine allows a task to disallow a task/fiber (including itself) from
* safely sharing any of the system's floating point registers with other
* tasks/fibers.
*
* WARNING
* This routine should only be used to disable floating point support for
* a task/fiber that currently has such support enabled.
*
* @return N/A
*/
FUNC_ALIAS(_FpDisable, task_float_disable, void);
/**
*
* @brief Handler for "device not available" exception
*
* This routine is registered to handle the "device not available" exception
* (vector = 7)
*
* The processor will generate this exception if any x87 FPU, MMX, or SSEx
* instruction is executed while CR0[TS]=1. The handler then enables the
* current task or fiber with the USE_FP option (or the USE_SSE option if the
* SSE configuration option has been enabled).
*
* @param pEsf this value is not used for this architecture
*
* @return N/A
*/
void _FpNotAvailableExcHandler(NANO_ESF * pEsf)
{
unsigned int enableOption;
ARG_UNUSED(pEsf);
/*
* Assume the exception did not occur in the thread of an ISR.
* (In other words, CPU cycles will not be consumed to perform
* error checking to ensure the exception was not generated in an ISR.)
*/
PRINTK("_FpNotAvailableExcHandler() exception handler has been "
"invoked\n");
/* Enable the highest level of FP capability configured into the kernel */
#ifdef CONFIG_SSE
enableOption = USE_SSE;
#else
enableOption = USE_FP;
#endif
_FpEnable(_nanokernel.current, enableOption);
}
#endif /* CONFIG_FP_SHARING */