@@ -14,14 +14,13 @@
#include <asm/fpu/types.h>
/*
* Use kernel_fpu_begin/end() if you intend to use FPU in kernel context. It
- * disables preemption so be careful if you intend to use it for long periods
- * of time.
- * If you intend to use the FPU in irq/softirq you need to check first with
- * irq_fpu_usable() if it is possible.
+ * disables preemption and softirq processing, so be careful if you intend to
+ * use it for long periods of time. Kernel-mode FPU cannot be used in all
+ * contexts -- see irq_fpu_usable() for details.
*/
/* Kernel FPU states to initialize in kernel_fpu_begin_mask() */
#define KFPU_387 _BITUL(0) /* 387 state will be initialized */
#define KFPU_MXCSR _BITUL(1) /* MXCSR will be initialized */
@@ -48,25 +47,23 @@ static inline void kernel_fpu_begin(void)
kernel_fpu_begin_mask(KFPU_387 | KFPU_MXCSR);
#endif
}
/*
- * Use fpregs_lock() while editing CPU's FPU registers or fpu->fpstate.
- * A context switch will (and softirq might) save CPU's FPU registers to
- * fpu->fpstate.regs and set TIF_NEED_FPU_LOAD leaving CPU's FPU registers in
- * a random state.
+ * Use fpregs_lock() while editing CPU's FPU registers or fpu->fpstate, or while
+ * using the FPU in kernel mode. A context switch will (and softirq might) save
+ * CPU's FPU registers to fpu->fpstate.regs and set TIF_NEED_FPU_LOAD leaving
+ * CPU's FPU registers in a random state.
*
* local_bh_disable() protects against both preemption and soft interrupts
* on !RT kernels.
*
* On RT kernels local_bh_disable() is not sufficient because it only
* serializes soft interrupt related sections via a local lock, but stays
* preemptible. Disabling preemption is the right choice here as bottom
* half processing is always in thread context on RT kernels so it
* implicitly prevents bottom half processing as well.
- *
- * Disabling preemption also serializes against kernel_fpu_begin().
*/
static inline void fpregs_lock(void)
{
if (!IS_ENABLED(CONFIG_PREEMPT_RT))
local_bh_disable();
@@ -55,35 +55,22 @@ DEFINE_PER_CPU(struct fpu *, fpu_fpregs_owner_ctx);
* Can we use the FPU in kernel mode with the
* whole "kernel_fpu_begin/end()" sequence?
*/
bool irq_fpu_usable(void)
{
- if (WARN_ON_ONCE(in_nmi()))
- return false;
-
- /* In kernel FPU usage already active? */
- if (this_cpu_read(in_kernel_fpu))
- return false;
-
/*
- * When not in NMI or hard interrupt context, FPU can be used in:
- *
- * - Task context except from within fpregs_lock()'ed critical
- * regions.
- *
- * - Soft interrupt processing context which cannot happen
- * while in a fpregs_lock()'ed critical region.
+ * kernel_fpu_begin() takes fpregs_lock(), which disables preemption and
+ * softirq processing. That prevents any other task or softirq from
+ * trying to use the FPU. Therefore, kernel-mode FPU can always be used
+ * in task and softirq context, except when hardirqs are disabled which
+ * is not compatible with disabling and enabling softirq processing, or
+ * when kernel-mode FPU is explicitly nested (which should never
+ * happen). Disabling/enabling softirq processing is also not allowed
+ * in hardirq context. Thus, we get the following condition.
*/
- if (!in_hardirq())
- return true;
-
- /*
- * In hard interrupt context it's safe when soft interrupts
- * are enabled, which means the interrupt did not hit in
- * a fpregs_lock()'ed critical region.
- */
- return !softirq_count();
+ return !this_cpu_read(in_kernel_fpu) &&
+ !in_hardirq() && !irqs_disabled() && !in_nmi();
}
EXPORT_SYMBOL(irq_fpu_usable);
/*
* Track AVX512 state use because it is known to slow the max clock
@@ -418,11 +405,11 @@ int fpu_copy_uabi_to_guest_fpstate(struct fpu_guest *gfpu, const void *buf,
EXPORT_SYMBOL_GPL(fpu_copy_uabi_to_guest_fpstate);
#endif /* CONFIG_KVM */
void kernel_fpu_begin_mask(unsigned int kfpu_mask)
{
- preempt_disable();
+ fpregs_lock();
WARN_ON_FPU(!irq_fpu_usable());
WARN_ON_FPU(this_cpu_read(in_kernel_fpu));
this_cpu_write(in_kernel_fpu, true);
@@ -446,11 +433,11 @@ EXPORT_SYMBOL_GPL(kernel_fpu_begin_mask);
void kernel_fpu_end(void)
{
WARN_ON_FPU(!this_cpu_read(in_kernel_fpu));
this_cpu_write(in_kernel_fpu, false);
- preempt_enable();
+ fpregs_unlock();
}
EXPORT_SYMBOL_GPL(kernel_fpu_end);
/*
* Sync the FPU register state to current's memory register state when the