/* * 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 #include #endif /* CONFIG_MICROKERNEL */ #include #include #include /* 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 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 */