Skip to contentSkip to conversation

framework for fpu usage in kernel

RFC In review Manfred Spraul 2 participants 4 messages Feb 11, 2001

lore
Share & tools
lore
#0 lore raw

RFC 1/1 framework for fpu usage in kernel

Currently there are 2 fpu users in the kernel:
raid5 checksumming and 3dnow memcpy/memset.

raid5 checksumming is not problematic, but _mmx_memcpy() has unexpected side effects if someone else is also using the fpu:
memcopy is a really generic function, and calling it saves the current fpu state into thread.i387.f{,x}save. IMHO that's wrong, memcopy must save into a local buffer like raid5 checksumming.

I've attached a proposal that supports arbitrary combinations of fpu users in kernel space.

* kernel threads can use the fpu freely. Nothing new.
* 2 sets of functions for fpu usage in "normal" threads:

- kfpu_acquire(), kfpu_try_acquire() + release functions.

- kfpu_full_begin(), kfpu_mmx_begin(), kfpu_nosave_begin(), kfpu_try_begin() + _end() functions.

The first set is only possible from normal process context. The caller can sleep between _acquire() and _release().

The second set can be called from arbitrary context, but the caller must not sleep between _begin() and _end().

The _try() functions check if the fpu is unused, and fail if the fpu is currently in use. That way the memcpy()/memset() functions can avoid fpu context saves/restores and it saves stack space.

Nesting is partially possible: _begin() within _acquire is possible, _acquire() within _begin() will BUG.

[_acquire()/_release() have similar restrictions as down() and up(), _begin()/_end() have similar restrictions as spinlocks]

The patch itself is alpha quality: only the sse functions are tested, it boots when compiled for Pentium III, the raid5 checksum _benchmark_ still works and distributed.net still cracks rc5.

I haven't yet checked that the exception handlers are still called properly.

What do you think?
--
Manfred

// $Header$
// Kernel Version:

//  VERSION = 2
//  PATCHLEVEL = 4
//  SUBLEVEL = 1
//  EXTRAVERSION =
include/asm-i386/i387.h +3 -4
diff -urN 2.4/include/asm-i386/i387.h build-2.4/include/asm-i386/i387.h
@@ -16,6 +16,7 @@
 #include <asm/sigcontext.h>
 #include <asm/user.h>
 
+#include <asm/kfpu.h>
 extern void init_fpu(void);
 /*
  * FPU lazy state save handling...
@@ -23,9 +24,7 @@
 extern void save_init_fpu( struct task_struct *tsk );
 extern void restore_fpu( struct task_struct *tsk );
 
-extern void kernel_fpu_begin(void);
-#define kernel_fpu_end() stts()
-
+#include <asm/kfpu.h>
 
 #define unlazy_fpu( tsk ) do { \
 	if ( tsk->flags & PF_USEDFPU ) \
@@ -36,7 +35,7 @@
 	if ( tsk->flags & PF_USEDFPU ) { \
 		asm volatile("fwait"); \
 		tsk->flags &= ~PF_USEDFPU; \
-		stts(); \
+		kfpu_leave(); \
 	} \
 } while (0)
 
include/asm-i386/kfpu.h +83 -0

83 lines not on this page · raw message

include/asm-i386/page.h +8 -1
diff -urN 2.4/include/asm-i386/page.h build-2.4/include/asm-i386/page.h
@@ -11,7 +11,14 @@
 
 #include <linux/config.h>
 
-#ifdef CONFIG_X86_USE_3DNOW
+#ifdef CONFIG_X86_USE_SSE
+
+#include <asm/sse.h>
+
+#define clear_page(page)	sse_clear_page(page)
+#define copy_page(to,from)	sse_copy_page(to,from)
+
+#elif defined(CONFIG_X86_USE_3DNOW)
 
 #include <asm/mmx.h>
 
include/asm-i386/sse.h +11 -0
diff -urN 2.4/include/asm-i386/sse.h build-2.4/include/asm-i386/sse.h
@@ -0,0 +1,11 @@
+#ifndef _ASM_SSE_H
+#define _ASM_SSE_H
+
+/*
+ *	SSE helper operations
+ */
+ 
+extern void sse_clear_page(void *page);
+extern void sse_copy_page(void *to, void *from);
+
+#endif
include/asm-i386/system.h +0 -2
diff -urN 2.4/include/asm-i386/system.h build-2.4/include/asm-i386/system.h
@@ -100,7 +100,6 @@
 /*
  * Clear and set 'TS' bit respectively
  */
-#define clts() __asm__ __volatile__ ("clts")
 #define read_cr0() ({ \
 	unsigned int __dummy; \
 	__asm__( \
@@ -110,7 +109,6 @@
 })
 #define write_cr0(x) \
 	__asm__("movl %0,%%cr0": :"r" (x));
-#define stts() write_cr0(8 | read_cr0())
 
 #endif	/* __KERNEL__ */
 
include/asm-i386/xor.h +37 -36

167 lines not on this page · raw message

arch/i386/lib/Makefile +1 -0
diff -urN --exclude .depend 2.4/arch/i386/lib/Makefile build-2.4/arch/i386/lib/Makefile
@@ -12,6 +12,7 @@
 	memcpy.o
 
 obj-$(CONFIG_X86_USE_3DNOW) += mmx.o
+obj-$(CONFIG_X86_USE_SSE) += sse.o
 obj-$(CONFIG_HAVE_DEC_LOCK) += dec_and_lock.o
 
 include $(TOPDIR)/Rules.make
arch/i386/lib/mmx.c +16 -17
diff -urN --exclude .depend 2.4/arch/i386/lib/mmx.c build-2.4/arch/i386/lib/mmx.c
@@ -28,7 +28,11 @@
 	void *p=to;
 	int i= len >> 6;	/* len/64 */
 
-	kernel_fpu_begin();
+	if(!kfpu_try_begin() {
+		/* FIXME: this belongs into string.h */
+		__memcpy(to, from, len);
+		return;
+	} 
 
 	__asm__ __volatile__ (
 		"1: prefetch (%0)\n"		/* This set is 28 bytes */
@@ -84,7 +88,7 @@
 	 *	Now do the tail of the block
 	 */
 	__memcpy(to, from, len&63);
-	kernel_fpu_end();
+	kfpu_try_end();
 	return p;
 }
 
@@ -92,8 +96,6 @@
 {
 	int i;
 
-	kernel_fpu_begin();
-	
 	__asm__ __volatile__ (
 		"  pxor %%mm0, %%mm0\n" : :
 	);
@@ -118,18 +120,12 @@
 	__asm__ __volatile__ (
 		"  sfence \n" : :
 	);
-	kernel_fpu_end();
 }
 
 static void fast_copy_page(void *to, void *from)
 {
 	int i;
 
-	kernel_fpu_begin();
-
-	/* maybe the prefetch stuff can go before the expensive fnsave...
-	 * but that is for later. -AV
-	 */
 	__asm__ __volatile__ (
 		"1: prefetch (%0)\n"
 		"   prefetch 64(%0)\n"
@@ -185,7 +181,6 @@
 	__asm__ __volatile__ (
 		"  sfence \n" : :
 	);
-	kernel_fpu_end();
 }
 
 /*
@@ -205,10 +200,12 @@
  
 void mmx_clear_page(void * page)
 {
-	if(in_interrupt())
-		slow_zero_page(page);
-	else
+	if (kfpu_try_begin()) {
 		fast_clear_page(page);
+		kfpu_try_end();
+	} else {
+		slow_zero_page(page);
+	}
 }
 
 static void slow_copy_page(void *to, void *from)
@@ -225,8 +222,10 @@
 
 void mmx_copy_page(void *to, void *from)
 {
-	if(in_interrupt())
-		slow_copy_page(to, from);
-	else
+	if (kfpu_try_begin()) {
 		fast_copy_page(to, from);
+		kfpu_try_end();
+	} else {
+		slow_copy_page(to, from);
+	}
 }
arch/i386/lib/sse.c +107 -0

107 lines not on this page · raw message

arch/i386/kernel/i387.c +191 -7

225 lines not on this page · raw message

arch/i386/kernel/traps.c +1 -1
@@ -731,7 +731,7 @@
  */
 asmlinkage void math_state_restore(struct pt_regs regs)
 {
-	__asm__ __volatile__("clts");		/* Allow maths ops (or we recurse) */
+	kfpu_enter();		/* Allow maths ops (or we recurse) */
 
 	if (current->used_math) {
 		restore_fpu(current);
arch/i386/kernel/setup.c +2 -1
@@ -2274,7 +2274,8 @@
 	 */
 	current->flags &= ~PF_USEDFPU;
 	current->used_math = 0;
-	stts();
+	kfpu_initialize();
+
 }
 
 /*
arch/i386/kernel/i386_ksyms.c +19 -0
@@ -117,6 +117,25 @@
 EXPORT_SYMBOL(mmx_copy_page);
 #endif
 
+#ifdef CONFIG_X86_USE_SSE
+EXPORT_SYMBOL(sse_clear_page);
+EXPORT_SYMBOL(sse_copy_page);
+#endif
+
+EXPORT_SYMBOL(kfpu_start);
+EXPORT_SYMBOL(kfpu_acquire);
+EXPORT_SYMBOL(kfpu_release);
+EXPORT_SYMBOL(kfpu_try_acquire);
+EXPORT_SYMBOL(kfpu_try_release);
+EXPORT_SYMBOL(kfpu_full_begin);
+EXPORT_SYMBOL(kfpu_mmx_begin);
+EXPORT_SYMBOL(kfpu_nosave_begin);
+EXPORT_SYMBOL(kfpu_full_end);
+EXPORT_SYMBOL(kfpu_mmx_end);
+EXPORT_SYMBOL(kfpu_nosave_end);
+EXPORT_SYMBOL(kfpu_try_begin);
+EXPORT_SYMBOL(kfpu_try_end);
+
 #ifdef CONFIG_SMP
 EXPORT_SYMBOL(cpu_data);
 EXPORT_SYMBOL(kernel_flag);
arch/i386/config.in +2 -0
@@ -91,6 +91,7 @@
    define_bool CONFIG_X86_GOOD_APIC y
    define_bool CONFIG_X86_PGE y
    define_bool CONFIG_X86_USE_PPRO_CHECKSUM y
+   define_bool CONFIG_X86_USE_SSE y
 fi
 if [ "$CONFIG_MPENTIUM4" = "y" ]; then
    define_int  CONFIG_X86_L1_CACHE_SHIFT 7
@@ -98,6 +99,7 @@
    define_bool CONFIG_X86_GOOD_APIC y
    define_bool CONFIG_X86_PGE y
    define_bool CONFIG_X86_USE_PPRO_CHECKSUM y
+   define_bool CONFIG_X86_USE_SSE y
 fi
 if [ "$CONFIG_MK6" = "y" ]; then
    define_int  CONFIG_X86_L1_CACHE_SHIFT 5
#1 lore raw
Quoted 3 lines

memcopy is a really generic function, and calling it saves the current fpu state into thread.i387.f{,x}save. IMHO that's wrong, memcopy must save into a local buffer like raid5 checksumming.

The mmx copy is only done in task context. There are a whole variety of _horrible_ problems doing it in interrupt space so based on the considerable number of problems with prior attempts to get it right on IRQ and copyuser cases I didnt bother
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
Please read the FAQ at http://www.tux.org/lkml/

#2 lore raw
Quoted 10 lines · Alan Cox
Quoted 4 lines

memcopy is a really generic function, and calling it saves the current fpu state into thread.i387.f{,x}save. IMHO that's wrong, memcopy must save into a local buffer like raid5 checksumming.

The mmx copy is only done in task context. There are a whole variety of _horrible_ problems doing it in interrupt space so based on the considerable number of problems with prior attempts to get it right on IRQ and copyuser cases I didnt bother

Even task context is dangerous:

What if a drm module wants to use the fpu and then uses memcpy() after modifying the ftp registers?

--
Manfred
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
Please read the FAQ at http://www.tux.org/lkml/

#3 lore raw
Quoted 2 lines

What if a drm module wants to use the fpu and then uses memcpy() after modifying the ftp registers?

Interesting question. Right now the answer is dont do that. Point noted

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
Please read the FAQ at http://www.tux.org/lkml/