Subject: [patch] linux: R3k DECstation FPU support

From: "Maciej W. Rozycki" <[email-masked]>
Date: Fri, 23 Nov 2001 19:46:42 +0100 (MET)

Hello,

 Here is an implementation of an FPU interrupt handler for R3k DECstations
(R4k ones are fine with the dedicated exception).  At first I thought only
interrupt to do_fpe() glue is missing but to my surprise I discovered no
R3k-class CPU is currently assumed to feature an FPU.  As making use of an
FPU is usually advantageous if one exists, I implemented an FPU detection
function following guidelines given by my MIPS manual (I can't test an
FPU-less system).

 I believe the code is stable as is.  It does not provide any additional
functionality beyond what is currently available to systems using the
dedicated FPU exception.  To verify an FPU is detected properly (the
kernel does not report it in any way now) and FPU exceptions are delivered
successfully the following program can be used. 

fdiv.c:

#include <stddef.h>
#include <stdio.h>

#include <fpu_control.h>

int main(void)
{
	double x, y, z;
	int r;
	fpu_control_t fpcw;
	unsigned int fpir;

	setvbuf(stdout, NULL, _IONBF, 0);

	_FPU_GETCW(fpcw);

	fpcw &= _FPU_RESERVED;
	fpcw |= _FPU_IEEE;

	_FPU_SETCW(fpcw);

	__asm__("cfc1 %0, $0"
		: "=r" (fpir)
		:
		: "memory");
	
	_FPU_GETCW(fpcw);

	printf("FPCW: %08x\n", fpcw);
	printf("FPIR: %08x\n", fpir);

	__asm__(".set push\n\t"
		".set reorder\n\t"
		"mtc1 %z4, %1\n\t"
		"mtc1 %z5, %2\n\t"
		"cvt.d.w %1, %1\n\t"
		"cvt.d.w %2, %2\n\t"
		".set noreorder\n\t"
		"b 0f\n\t"
		" div.d %3, %1, %2\n\t"
		".set reorder\n\t"
		"nop\n"
		"0:\n\t"
		"cvt.w.d %3, %3\n\t"
		"mfc1 %0, %3\n\t"
		".set pop"
		: "=r" (r), "=f" (x), "=f" (y), "=f" (z)
		: "Jr" (0), "Jr" (0));

	return r;
}

You should receive an output similar to one of the following ones:

FPCW: 00000f80
FPIR: 00000340
Floating point exception

for an R3k-class FPU,

FPCW: 00000f80
FPIR: 00000500
Floating point exception

for an R4k-class FPU,

FPCW: 00000f80
FPIR: 00000000
Floating point exception

for the FPU emulation (the Implementation and Revision register is zero). 
If you don't get an exception then there is a problem with FPU interrupt
delivery -- please report it to me, especially if it happens for a
DECstation. 

 The patch includes a few minor clean-ups of nearby code as well.

  Maciej

-- 
+  Maciej W. Rozycki, Technical University of Gdansk, Poland   +
+--------------------------------------------------------------+
+        e-mail: [email-masked], PGP key available        +

patch-mips-2.4.14-20011123-dec-fpu-9
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/arch/mips/dec/int-handler.S linux-mips-2.4.14-20011123-dec-fpu/arch/mips/dec/int-handler.S
--- linux-mips-2.4.14-20011123-dist/arch/mips/dec/int-handler.S	Tue Jul  3 04:27:16 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/arch/mips/dec/int-handler.S	Fri Nov 23 16:35:53 2001
@@ -2,7 +2,7 @@
  * arch/mips/dec/int-handler.S
  *
  * Copyright (C) 1995, 1996, 1997 Paul M. Antoine and Harald Koerfgen
- * Copyright (C) 2000  Maciej W. Rozycki
+ * Copyright (C) 2000, 2001  Maciej W. Rozycki
  *
  * Written by Ralf Baechle and Andreas Busse, modified for DECStation
  * support by Paul Antoine and Harald Koerfgen.
@@ -145,6 +145,9 @@
 
 		beqz	t0,spurious
 
+		 andi	t2,t0,DEC_IE_FPU
+		bnez	t2,fpu			# handle FPU immediately
+
 		/*
 		 * Find irq with highest priority
 		 */
@@ -169,7 +172,7 @@
  * Handle "IRQ Controller" Interrupts
  * Masked Interrupts are still visible and have to be masked "by hand".
  */
-		EXPORT(kn02_io_int)
+		FEXPORT(kn02_io_int)
 kn02_io_int:					# 3max
 		lui	t0,KN02_CSR_ADDR>>16	# get interrupt status and mask
 		lw	t0,(t0)
@@ -179,7 +182,7 @@ kn02_io_int:					# 3max
 		b	find_int
 		 and	t0,t3			# mask out allowed ones
 
-		EXPORT(kn03_io_int)
+		FEXPORT(kn03_io_int)
 kn03_io_int:					# 3max+
 		lui	t2,KN03_IOASIC_BASE>>16	# upper part of IOASIC Address
 		lw	t0,SIR(t2)		# get status: IOASIC isr
@@ -188,7 +191,7 @@ kn03_io_int:					# 3max+
 		b	find_int
 		 and	t0,t3			# mask out allowed ones
 
-		EXPORT(kn02xa_io_int)
+		FEXPORT(kn02xa_io_int)
 kn02xa_io_int:					# 3min/maxine
 		lui	t2,KN02XA_IOASIC_BASE>>16		
 						# upper part of IOASIC Address
@@ -219,28 +222,27 @@ handle_it:	jal	do_IRQ
 		j	ret_from_irq
 		 nop
 
+fpu:
+		j	handle_fpe_int
+		 nop
+
 spurious:
 		j	spurious_interrupt
 		 nop
 		END(decstation_handle_int)
-/*
-  * Interrupt routines common to all DECStations first.
- */
-		EXPORT(dec_intr_fpu)
-dec_intr_fpu:	PANIC("Unimplemented FPU interrupt handler")
 
 /*
  * Generic unimplemented interrupt routines - ivec_tbl is initialised to
  * point all interrupts here.  The table is then filled in by machine-specific
  * initialisation in dec_setup().
  */
-		EXPORT(dec_intr_unimplemented)
+		FEXPORT(dec_intr_unimplemented)
 dec_intr_unimplemented:
 		mfc0	a1,CP0_CAUSE		# cheats way of printing an arg!
 		nop				# to be sure...
 		PANIC("Unimplemented cpu interrupt! CP0_CAUSE: 0x%x");
 
-		EXPORT(asic_intr_unimplemented)
+		FEXPORT(asic_intr_unimplemented)
 asic_intr_unimplemented:
 		move	a1,t0			# cheats way of printing an arg!
 		PANIC("Unimplemented asic interrupt! ASIC ISR: 0x%x");
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/arch/mips/dec/setup.c linux-mips-2.4.14-20011123-dec-fpu/arch/mips/dec/setup.c
--- linux-mips-2.4.14-20011123-dist/arch/mips/dec/setup.c	Tue Jul  3 04:27:16 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/arch/mips/dec/setup.c	Thu Nov 22 00:20:46 2001
@@ -6,7 +6,7 @@
  * for more details.
  *
  * Copyright (C) 1998 Harald Koerfgen
- * Copyright (C) 2000 Maciej W. Rozycki
+ * Copyright (C) 2000, 2001 Maciej W. Rozycki
  */
 #include <linux/sched.h>
 #include <linux/interrupt.h>
@@ -55,7 +55,7 @@ extern int setup_dec_irq(int, struct irq
 
 void (*board_time_init) (struct irqaction * irq);
 
-static struct irqaction irq10 = {dec_intr_halt, 0, 0, "halt", NULL, NULL};
+static struct irqaction haltirq = {dec_intr_halt, 0, 0, "halt", NULL, NULL};
 
 /*
  * enable the periodic interrupts
@@ -139,10 +139,10 @@ void __init dec_init_kn01(void)
     cpu_mask_tbl[4] = IE_IRQ4;
     cpu_irq_nr[4] = MEMORY;
 
-    dec_interrupt[FPU].cpu_mask = IE_IRQ5;
-    dec_interrupt[FPU].iemask = 0;
-    cpu_mask_tbl[5] = IE_IRQ5;
-    cpu_irq_nr[5] = FPU;
+    /*
+     * Enable board interrupts: FPU.
+     */
+    set_cp0_status(DEC_IE_FPU);
 }				/* dec_init_kn01 */
 
 /*
@@ -165,10 +165,10 @@ void __init dec_init_kn230(void)
     cpu_mask_tbl[0] = IE_IRQ2;
     cpu_irq_nr[0] = CLOCK;
 
-    dec_interrupt[FPU].cpu_mask = IE_IRQ5;
-    dec_interrupt[FPU].iemask = 0;
-    cpu_mask_tbl[5] = IE_IRQ5;
-    cpu_irq_nr[5] = FPU;
+    /*
+     * Enable board interrupts: FPU.
+     */
+    set_cp0_status(DEC_IE_FPU);
 }				/* dec_init_kn230 */
 
 /*
@@ -176,6 +176,8 @@ void __init dec_init_kn230(void)
  */
 void __init dec_init_kn02(void)
 {
+    int dec_ie_io;
+
     /*
      * Setup some memory addresses. FIXME: probably incomplete!
      */
@@ -184,10 +186,11 @@ void __init dec_init_kn02(void)
     imr = (void *) KN02_CSR_ADDR;
 
     /*
-     * Setup IOASIC interrupt
+     * Setup I/O interrupt
      */
+    dec_ie_io = IE_IRQ0;
     cpu_ivec_tbl[1] = kn02_io_int;
-    cpu_mask_tbl[1] = IE_IRQ0;
+    cpu_mask_tbl[1] = dec_ie_io;
     cpu_irq_nr[1] = -1;
     *imr = *imr & 0xff00ff00;
 
@@ -234,11 +237,10 @@ void __init dec_init_kn02(void)
     cpu_mask_tbl[2] = IE_IRQ3;
     cpu_irq_nr[2] = MEMORY;
 
-    dec_interrupt[FPU].cpu_mask = IE_IRQ5;
-    dec_interrupt[FPU].iemask = 0;
-    cpu_mask_tbl[3] = IE_IRQ5;
-    cpu_irq_nr[3] = FPU;
-
+    /*
+     * Enable board interrupts: FPU, I/O.
+     */
+    set_cp0_status(DEC_IE_FPU | dec_ie_io);
 }				/* dec_init_kn02 */
 
 /*
@@ -246,6 +248,8 @@ void __init dec_init_kn02(void)
  */
 void __init dec_init_kn02ba(void)
 {
+    int dec_ie_ioasic;
+
     /*
      * Setup some memory addresses.
      */
@@ -257,9 +261,10 @@ void __init dec_init_kn02ba(void)
     /*
      * Setup IOASIC interrupt
      */
-    cpu_mask_tbl[0] = IE_IRQ3;
-    cpu_irq_nr[0] = -1;
+    dec_ie_ioasic = IE_IRQ3;
     cpu_ivec_tbl[0] = kn02xa_io_int;
+    cpu_mask_tbl[0] = dec_ie_ioasic;
+    cpu_irq_nr[0] = -1;
     *imr = 0;
 
     /*
@@ -315,12 +320,12 @@ void __init dec_init_kn02ba(void)
     cpu_mask_tbl[4] = IE_IRQ4;
     cpu_irq_nr[4] = HALT;
 
-    dec_interrupt[FPU].cpu_mask = IE_IRQ5;
-    dec_interrupt[FPU].iemask = 0;
-    cpu_mask_tbl[5] = IE_IRQ5;
-    cpu_irq_nr[5] = FPU;
+    /*
+     * Enable board interrupts: FPU, I/O ASIC.
+     */
+    set_cp0_status(DEC_IE_FPU | dec_ie_ioasic);
 
-    dec_halt_init(&irq10);
+    dec_halt_init(&haltirq);
 }				/* dec_init_kn02ba */
 
 /*
@@ -328,6 +333,8 @@ void __init dec_init_kn02ba(void)
  */
 void __init dec_init_kn02ca(void)
 {
+    int dec_ie_ioasic;
+
     /*
      * Setup some memory addresses. FIXME: probably incomplete!
      */
@@ -339,9 +346,10 @@ void __init dec_init_kn02ca(void)
     /*
      * Setup IOASIC interrupt
      */
+    dec_ie_ioasic = IE_IRQ3;
     cpu_ivec_tbl[1] = kn02xa_io_int;
+    cpu_mask_tbl[1] = dec_ie_ioasic;
     cpu_irq_nr[1] = -1;
-    cpu_mask_tbl[1] = IE_IRQ3;
     *imr = 0;
 
     /*
@@ -392,12 +400,12 @@ void __init dec_init_kn02ca(void)
     cpu_mask_tbl[3] = IE_IRQ4;
     cpu_irq_nr[3] = HALT;
 
-    dec_interrupt[FPU].cpu_mask = IE_IRQ5;
-    dec_interrupt[FPU].iemask = 0;
-    cpu_mask_tbl[4] = IE_IRQ5;
-    cpu_irq_nr[4] = FPU;
+    /*
+     * Enable board interrupts: FPU, I/O ASIC.
+     */
+    set_cp0_status(DEC_IE_FPU | dec_ie_ioasic);
 
-    dec_halt_init(&irq10);
+    dec_halt_init(&haltirq);
 }				/* dec_init_kn02ca */
 
 /*
@@ -405,6 +413,8 @@ void __init dec_init_kn02ca(void)
  */
 void __init dec_init_kn03(void)
 {
+    int dec_ie_ioasic;
+
     /*
      * Setup some memory addresses. FIXME: probably incomplete!
      */
@@ -416,8 +426,9 @@ void __init dec_init_kn03(void)
     /*
      * Setup IOASIC interrupt
      */
+    dec_ie_ioasic = IE_IRQ0;
     cpu_ivec_tbl[1] = kn03_io_int;
-    cpu_mask_tbl[1] = IE_IRQ0;
+    cpu_mask_tbl[1] = dec_ie_ioasic;
     cpu_irq_nr[1] = -1;
     *imr = 0;
 
@@ -474,10 +485,10 @@ void __init dec_init_kn03(void)
     cpu_mask_tbl[3] = IE_IRQ4;
     cpu_irq_nr[3] = HALT;
 
-    dec_interrupt[FPU].cpu_mask = IE_IRQ5;
-    dec_interrupt[FPU].iemask = 0;
-    cpu_mask_tbl[4] = IE_IRQ5;
-    cpu_irq_nr[4] = FPU;
+    /*
+     * Enable board interrupts: FPU, I/O ASIC.
+     */
+    set_cp0_status(DEC_IE_FPU | dec_ie_ioasic);
 
-    dec_halt_init(&irq10);
+    dec_halt_init(&haltirq);
 }				/* dec_init_kn03 */
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/arch/mips/kernel/entry.S linux-mips-2.4.14-20011123-dec-fpu/arch/mips/kernel/entry.S
--- linux-mips-2.4.14-20011123-dist/arch/mips/kernel/entry.S	Tue Nov 20 05:26:20 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/arch/mips/kernel/entry.S	Fri Nov 23 14:50:11 2001
@@ -106,16 +106,16 @@ LEAF(spurious_interrupt)
 
 		__INIT
 
-		/*
-		 * General exception vector.  Used for all CPUs except R4000
-		 * and R4400 SC and MC versions.
-		 */
 		.set	reorder
 
 		NESTED(except_vec1_generic, 0, sp)
 		PANIC("Exception vector 1 called")
 		END(except_vec1_generic)
 
+		/*
+		 * General exception vector.  Used for all CPUs except R4000
+		 * and R4400 SC and MC versions.
+		 */
 		NESTED(except_vec3_generic, 0, sp)
 		mfc0	k1, CP0_CAUSE
 		la	k0, exception_handlers
@@ -225,6 +225,7 @@ EXPORT(exception_count_##exception);    
 		NESTED(handle_##exception, PT_SIZE, sp);                \
 		.set	noat;                                           \
 		SAVE_ALL;                                               \
+		FEXPORT(handle_##exception##_int);			\
 		__BUILD_clear_##clear(exception);                       \
 		.set	at;                                             \
 		__BUILD_##verbose(exception);                           \
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/arch/mips/kernel/setup.c linux-mips-2.4.14-20011123-dec-fpu/arch/mips/kernel/setup.c
--- linux-mips-2.4.14-20011123-dist/arch/mips/kernel/setup.c	Tue Nov 20 05:26:20 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/arch/mips/kernel/setup.c	Fri Nov 23 14:50:11 2001
@@ -7,7 +7,7 @@
  * Copyright (C) 1995  Waldorf Electronics
  * Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001  Ralf Baechle
  * Copyright (C) 1996  Stoned Elipot
- * Copyright (C) 2000  Maciej W. Rozycki
+ * Copyright (C) 2000, 2001  Maciej W. Rozycki
  */
 #include <linux/config.h>
 #include <linux/errno.h>
@@ -182,6 +182,28 @@ static inline int cpu_has_confreg(void)
 #endif
 }
 
+/*
+ * Get the FPU Implementation/Revision.
+ */
+static inline unsigned long cpu_get_fpu_id(void)
+{
+	unsigned long tmp, fpu_id;
+
+	tmp = read_32bit_cp0_register(CP0_STATUS);
+	write_32bit_cp0_register(CP0_STATUS, tmp | ST0_CU1);
+	fpu_id = read_32bit_cp1_register(CP1_REVISION);
+	write_32bit_cp0_register(CP0_STATUS, tmp);
+	return fpu_id;
+}
+
+/*
+ * Check the CPU has an FPU the official way.
+ */
+static inline int cpu_has_fpu(void)
+{
+	return ((cpu_get_fpu_id() & 0xff00) != FPIR_IMP_NONE);
+}
+
 /* declaration of the global struct */
 struct mips_cpu mips_cpu = {PRID_IMP_UNKNOWN, CPU_UNKNOWN, 0, 0, 0,
 			    {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}};
@@ -206,6 +228,8 @@ static inline void cpu_probe(void)
 			mips_cpu.cputype = CPU_R2000;
 			mips_cpu.isa_level = MIPS_CPU_ISA_I;
 			mips_cpu.options = MIPS_CPU_TLB;
+			if (cpu_has_fpu())
+				mips_cpu.options |= MIPS_CPU_FPU;
 			mips_cpu.tlbsize = 64;
 			break;
 		case PRID_IMP_R3000:
@@ -218,6 +242,8 @@ static inline void cpu_probe(void)
 				mips_cpu.cputype = CPU_R3000;
 			mips_cpu.isa_level = MIPS_CPU_ISA_I;
 			mips_cpu.options = MIPS_CPU_TLB;
+			if (cpu_has_fpu())
+				mips_cpu.options |= MIPS_CPU_FPU;
 			mips_cpu.tlbsize = 64;
 			break;
 		case PRID_IMP_R4000:
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/arch/mips/kernel/traps.c linux-mips-2.4.14-20011123-dec-fpu/arch/mips/kernel/traps.c
--- linux-mips-2.4.14-20011123-dist/arch/mips/kernel/traps.c	Wed Nov 21 05:26:46 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/arch/mips/kernel/traps.c	Fri Nov 23 14:50:11 2001
@@ -501,7 +501,6 @@ asmlinkage void do_fpe(struct pt_regs *r
 		return;
 
 	force_sig(SIGFPE, current);
-	printk(KERN_DEBUG "Sent send SIGFPE to %s\n", current->comm);
 }
 
 static inline int get_insn_opcode(struct pt_regs *regs, unsigned int *opcode)
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/include/asm-mips/cpu.h linux-mips-2.4.14-20011123-dec-fpu/include/asm-mips/cpu.h
--- linux-mips-2.4.14-20011123-dist/include/asm-mips/cpu.h	Fri Nov 23 14:46:34 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/include/asm-mips/cpu.h	Fri Nov 23 15:08:18 2001
@@ -90,6 +90,17 @@
 #define PRID_REV_TX3927C 	0x0042
 #define PRID_REV_TX39H3TEG 	0x0050
 
+/*
+ * FPU implementation/revision register (CP1 control register 0).
+ *
+ * +---------------------------------+----------------+----------------+
+ * | 0                               | Implementation | Revision       |
+ * +---------------------------------+----------------+----------------+
+ *  31                             16 15             8 7              0
+ */
+
+#define FPIR_IMP_NONE		0x0000
+
 #ifndef  _LANGUAGE_ASSEMBLY
 /*
  * Capability and feature descriptor structure for MIPS CPU
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/include/asm-mips/dec/interrupts.h linux-mips-2.4.14-20011123-dec-fpu/include/asm-mips/dec/interrupts.h
--- linux-mips-2.4.14-20011123-dist/include/asm-mips/dec/interrupts.h	Tue Jul  3 04:27:22 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/include/asm-mips/dec/interrupts.h	Wed Nov 21 23:57:52 2001
@@ -13,6 +13,8 @@
 #ifndef __ASM_DEC_INTERRUPTS_H 
 #define __ASM_DEC_INTERRUPTS_H 
 
+#include <asm/mipsregs.h>
+
 /*
  * DECstation Interrupts
  */
@@ -22,7 +24,7 @@
  * Exception: on kmins we have to handle Memory Error 
  * Interrupts before the TC Interrupts.
  */
-#define CLOCK 	0
+#define CLOCK	 	0
 #define SCSI_DMA_INT 	1
 #define SCSI_INT	2
 #define ETHER		3
@@ -31,10 +33,17 @@
 #define TC1		6
 #define TC2		7
 #define MEMORY		8
-#define FPU		9
-#define HALT		10
+#define HALT		9
+
+#define NR_INTS		10
 
-#define NR_INTS	11
+/*
+ * The FPU is special.  It must always be handled first.
+ * Since it bypasses the regular IRQ handler we define
+ * the line it uses here.  All DECstations use the same
+ * one.
+ */
+#define DEC_IE_FPU	IE_IRQ5
 
 #ifndef __ASSEMBLY__
 /*
@@ -70,8 +79,6 @@ extern long asic_mask_tbl[32];
  * Common interrupt routine prototypes for all DECStations
  */
 extern void	dec_intr_unimplemented(void);
-extern void	dec_intr_fpu(void);
-extern void	dec_intr_rtc(void);
 
 extern void	kn02_io_int(void);
 extern void	kn02xa_io_int(void);
diff -up --recursive --new-file linux-mips-2.4.14-20011123-dist/include/asm-mips/mipsregs.h linux-mips-2.4.14-20011123-dec-fpu/include/asm-mips/mipsregs.h
--- linux-mips-2.4.14-20011123-dist/include/asm-mips/mipsregs.h	Tue Nov 20 05:27:07 2001
+++ linux-mips-2.4.14-20011123-dec-fpu/include/asm-mips/mipsregs.h	Fri Nov 23 14:55:27 2001
@@ -508,6 +508,19 @@
 	:"=r" (__res));                                         \
         __res;})
 
+/*
+ * Macros to access the floating point coprocessor control registers
+ */
+#define read_32bit_cp1_register(source)                         \
+({ int __res;                                                   \
+        __asm__ __volatile__(                                   \
+	".set\tpush\n\t"					\
+	".set\treorder\n\t"					\
+        "cfc1\t%0,"STR(source)"\n\t"                            \
+	".set\tpop"						\
+        : "=r" (__res));                                        \
+        __res;})
+
 /* TLB operations. */
 static inline void tlb_probe(void)
 {