Commit 20d60d99 authored by Maciej W. Rozycki's avatar Maciej W. Rozycki Committed by Ralf Baechle

[MIPS] R4000/R4400 errata workarounds

 This is the gereric part of R4000/R4400 errata workarounds.  They include 
compiler and assembler support as well as some source code modifications 
to address the problems with some combinations of multiply/divide+shift 
instructions as well as the daddi and daddiu instructions.

 Changes included are as follows:

1. New Kconfig options to select workarounds by platforms as necessary.

2. Arch top-level Makefile to pass necessary options to the compiler; also 
   incompatible configurations are detected (-mno-sym32 unsupported as 
   horribly intrusive for little gain).

3. Bug detection updated and shuffled -- the multiply/divide+shift problem 
   is lethal enough that if not worked around it makes the kernel crash in 
   time_init() because of a division by zero; the daddiu erratum might 
   also trigger early potentially, though I have not observed it.  On the 
   other hand the daddi detection code requires the exception subsystem to 
   have been initialised (and is there mainly for information).

4. r4k_daddiu_bug() added so that the existence of the erratum can be 
   queried by code at the run time as necessary; useful for generated code 
   like TLB fault and copy/clear page handlers.

5. __udelay() updated as it uses multiplication in inline assembly.

 Note that -mdaddi requires modified toolchain (which has been maintained 
by myself and available from my site for ~4years now -- versions covered 
are GCC 2.95.4 - 4.1.2 and binutils from 2.13 onwards).  The -mfix-r4000 
and -mfix-r4400 have been standard for a while though.
Signed-off-by: default avatarMaciej W. Rozycki <macro@linux-mips.org>
Signed-off-by: default avatarRalf Baechle <ralf@linux-mips.org>
parent 2b22c034
...@@ -91,6 +91,9 @@ config MACH_DECSTATION ...@@ -91,6 +91,9 @@ config MACH_DECSTATION
select BOOT_ELF32 select BOOT_ELF32
select CEVT_R4K select CEVT_R4K
select CSRC_R4K select CSRC_R4K
select CPU_DADDI_WORKAROUNDS if 64BIT
select CPU_R4000_WORKAROUNDS if 64BIT
select CPU_R4400_WORKAROUNDS if 64BIT
select DMA_NONCOHERENT select DMA_NONCOHERENT
select NO_IOPORT select NO_IOPORT
select IRQ_CPU select IRQ_CPU
...@@ -1605,6 +1608,19 @@ config CPU_HAS_SYNC ...@@ -1605,6 +1608,19 @@ config CPU_HAS_SYNC
config GENERIC_CLOCKEVENTS_BROADCAST config GENERIC_CLOCKEVENTS_BROADCAST
bool bool
#
# CPU non-features
#
config CPU_DADDI_WORKAROUNDS
bool
config CPU_R4000_WORKAROUNDS
bool
select CPU_R4400_WORKAROUNDS
config CPU_R4400_WORKAROUNDS
bool
# #
# Use the generic interrupt handling code in kernel/irq/: # Use the generic interrupt handling code in kernel/irq/:
# #
......
...@@ -141,6 +141,10 @@ cflags-$(CONFIG_CPU_R8000) += -march=r8000 -Wa,--trap ...@@ -141,6 +141,10 @@ cflags-$(CONFIG_CPU_R8000) += -march=r8000 -Wa,--trap
cflags-$(CONFIG_CPU_R10000) += $(call cc-option,-march=r10000,-march=r8000) \ cflags-$(CONFIG_CPU_R10000) += $(call cc-option,-march=r10000,-march=r8000) \
-Wa,--trap -Wa,--trap
cflags-$(CONFIG_CPU_R4000_WORKAROUNDS) += $(call cc-option,-mfix-r4000,)
cflags-$(CONFIG_CPU_R4400_WORKAROUNDS) += $(call cc-option,-mfix-r4400,)
cflags-$(CONFIG_CPU_DADDI_WORKAROUNDS) += $(call cc-option,-mno-daddi,)
ifdef CONFIG_CPU_SB1 ifdef CONFIG_CPU_SB1
ifdef CONFIG_SB1_PASS_1_WORKAROUNDS ifdef CONFIG_SB1_PASS_1_WORKAROUNDS
MODFLAGS += -msb1-pass1-workarounds MODFLAGS += -msb1-pass1-workarounds
...@@ -602,9 +606,11 @@ ifdef CONFIG_64BIT ...@@ -602,9 +606,11 @@ ifdef CONFIG_64BIT
endif endif
endif endif
ifeq ($(KBUILD_SYM32), y) ifeq ($(KBUILD_SYM32)$(call cc-option-yn,-msym32), yy)
ifeq ($(call cc-option-yn,-msym32), y)
cflags-y += -msym32 -DKBUILD_64BIT_SYM32 cflags-y += -msym32 -DKBUILD_64BIT_SYM32
else
ifeq ($(CONFIG_CPU_DADDI_WORKAROUNDS), y)
$(error CONFIG_CPU_DADDI_WORKAROUNDS unsupported without -msym32)
endif endif
endif endif
endif endif
......
...@@ -18,6 +18,15 @@ ...@@ -18,6 +18,15 @@
#include <asm/mipsregs.h> #include <asm/mipsregs.h>
#include <asm/system.h> #include <asm/system.h>
static char bug64hit[] __initdata =
"reliable operation impossible!\n%s";
static char nowar[] __initdata =
"Please report to <linux-mips@linux-mips.org>.";
static char r4kwar[] __initdata =
"Enable CPU_R4000_WORKAROUNDS to rectify.";
static char daddiwar[] __initdata =
"Enable CPU_DADDI_WORKAROUNDS to rectify.";
static inline void align_mod(const int align, const int mod) static inline void align_mod(const int align, const int mod)
{ {
asm volatile( asm volatile(
...@@ -155,13 +164,7 @@ static inline void check_mult_sh(void) ...@@ -155,13 +164,7 @@ static inline void check_mult_sh(void)
} }
printk("no.\n"); printk("no.\n");
panic("Reliable operation impossible!\n" panic(bug64hit, !R4000_WAR ? r4kwar : nowar);
#ifndef CONFIG_CPU_R4000
"Configure for R4000 to enable the workaround."
#else
"Please report to <linux-mips@linux-mips.org>."
#endif
);
} }
static volatile int daddi_ov __initdata = 0; static volatile int daddi_ov __initdata = 0;
...@@ -233,15 +236,11 @@ static inline void check_daddi(void) ...@@ -233,15 +236,11 @@ static inline void check_daddi(void)
} }
printk("no.\n"); printk("no.\n");
panic("Reliable operation impossible!\n" panic(bug64hit, !DADDI_WAR ? daddiwar : nowar);
#if !defined(CONFIG_CPU_R4000) && !defined(CONFIG_CPU_R4400)
"Configure for R4000 or R4400 to enable the workaround."
#else
"Please report to <linux-mips@linux-mips.org>."
#endif
);
} }
int daddiu_bug __initdata = -1;
static inline void check_daddiu(void) static inline void check_daddiu(void)
{ {
long v, w, tmp; long v, w, tmp;
...@@ -281,7 +280,9 @@ static inline void check_daddiu(void) ...@@ -281,7 +280,9 @@ static inline void check_daddiu(void)
: "=&r" (v), "=&r" (w), "=&r" (tmp) : "=&r" (v), "=&r" (w), "=&r" (tmp)
: "I" (0xffffffffffffdb9aUL), "I" (0x1234)); : "I" (0xffffffffffffdb9aUL), "I" (0x1234));
if (v == w) { daddiu_bug = v != w;
if (!daddiu_bug) {
printk("no.\n"); printk("no.\n");
return; return;
} }
...@@ -303,18 +304,16 @@ static inline void check_daddiu(void) ...@@ -303,18 +304,16 @@ static inline void check_daddiu(void)
} }
printk("no.\n"); printk("no.\n");
panic("Reliable operation impossible!\n" panic(bug64hit, !DADDI_WAR ? daddiwar : nowar);
#if !defined(CONFIG_CPU_R4000) && !defined(CONFIG_CPU_R4400)
"Configure for R4000 or R4400 to enable the workaround."
#else
"Please report to <linux-mips@linux-mips.org>."
#endif
);
} }
void __init check_bugs64(void) void __init check_bugs64_early(void)
{ {
check_mult_sh(); check_mult_sh();
check_daddi();
check_daddiu(); check_daddiu();
} }
void __init check_bugs64(void)
{
check_daddi();
}
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* Copyright (C) 1994, 95, 96, 97, 98, 99, 2000, 01, 02, 03 Ralf Baechle * Copyright (C) 1994, 95, 96, 97, 98, 99, 2000, 01, 02, 03 Ralf Baechle
* Copyright (C) 1996 Stoned Elipot * Copyright (C) 1996 Stoned Elipot
* Copyright (C) 1999 Silicon Graphics, Inc. * Copyright (C) 1999 Silicon Graphics, Inc.
* Copyright (C) 2000 2001, 2002 Maciej W. Rozycki * Copyright (C) 2000, 2001, 2002, 2007 Maciej W. Rozycki
*/ */
#include <linux/init.h> #include <linux/init.h>
#include <linux/ioport.h> #include <linux/ioport.h>
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <asm/addrspace.h> #include <asm/addrspace.h>
#include <asm/bootinfo.h> #include <asm/bootinfo.h>
#include <asm/bugs.h>
#include <asm/cache.h> #include <asm/cache.h>
#include <asm/cpu.h> #include <asm/cpu.h>
#include <asm/sections.h> #include <asm/sections.h>
...@@ -561,6 +562,7 @@ void __init setup_arch(char **cmdline_p) ...@@ -561,6 +562,7 @@ void __init setup_arch(char **cmdline_p)
} }
#endif #endif
cpu_report(); cpu_report();
check_bugs_early();
#if defined(CONFIG_VT) #if defined(CONFIG_VT)
#if defined(CONFIG_VGA_CONSOLE) #if defined(CONFIG_VGA_CONSOLE)
......
/* /*
* This is included by init/main.c to check for architecture-dependent bugs. * This is included by init/main.c to check for architecture-dependent bugs.
* *
* Copyright (C) 2007 Maciej W. Rozycki
*
* Needs: * Needs:
* void check_bugs(void); * void check_bugs(void);
*/ */
#ifndef _ASM_BUGS_H #ifndef _ASM_BUGS_H
#define _ASM_BUGS_H #define _ASM_BUGS_H
#include <linux/bug.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <asm/cpu.h> #include <asm/cpu.h>
#include <asm/cpu-info.h> #include <asm/cpu-info.h>
extern int daddiu_bug;
extern void check_bugs64_early(void);
extern void check_bugs32(void); extern void check_bugs32(void);
extern void check_bugs64(void); extern void check_bugs64(void);
static inline void check_bugs_early(void)
{
#ifdef CONFIG_64BIT
check_bugs64_early();
#endif
}
static inline void check_bugs(void) static inline void check_bugs(void)
{ {
unsigned int cpu = smp_processor_id(); unsigned int cpu = smp_processor_id();
...@@ -25,4 +40,14 @@ static inline void check_bugs(void) ...@@ -25,4 +40,14 @@ static inline void check_bugs(void)
#endif #endif
} }
static inline int r4k_daddiu_bug(void)
{
#ifdef CONFIG_64BIT
WARN_ON(daddiu_bug < 0);
return daddiu_bug != 0;
#else
return 0;
#endif
}
#endif /* _ASM_BUGS_H */ #endif /* _ASM_BUGS_H */
...@@ -6,13 +6,16 @@ ...@@ -6,13 +6,16 @@
* Copyright (C) 1994 by Waldorf Electronics * Copyright (C) 1994 by Waldorf Electronics
* Copyright (C) 1995 - 2000, 01, 03 by Ralf Baechle * Copyright (C) 1995 - 2000, 01, 03 by Ralf Baechle
* Copyright (C) 1999, 2000 Silicon Graphics, Inc. * Copyright (C) 1999, 2000 Silicon Graphics, Inc.
* Copyright (C) 2007 Maciej W. Rozycki
*/ */
#ifndef _ASM_DELAY_H #ifndef _ASM_DELAY_H
#define _ASM_DELAY_H #define _ASM_DELAY_H
#include <linux/param.h> #include <linux/param.h>
#include <linux/smp.h> #include <linux/smp.h>
#include <asm/compiler.h> #include <asm/compiler.h>
#include <asm/war.h>
static inline void __delay(unsigned long loops) static inline void __delay(unsigned long loops)
{ {
...@@ -50,7 +53,7 @@ static inline void __delay(unsigned long loops) ...@@ -50,7 +53,7 @@ static inline void __delay(unsigned long loops)
static inline void __udelay(unsigned long usecs, unsigned long lpj) static inline void __udelay(unsigned long usecs, unsigned long lpj)
{ {
unsigned long lo; unsigned long hi, lo;
/* /*
* The rates of 128 is rounded wrongly by the catchall case * The rates of 128 is rounded wrongly by the catchall case
...@@ -70,11 +73,16 @@ static inline void __udelay(unsigned long usecs, unsigned long lpj) ...@@ -70,11 +73,16 @@ static inline void __udelay(unsigned long usecs, unsigned long lpj)
: "=h" (usecs), "=l" (lo) : "=h" (usecs), "=l" (lo)
: "r" (usecs), "r" (lpj) : "r" (usecs), "r" (lpj)
: GCC_REG_ACCUM); : GCC_REG_ACCUM);
else if (sizeof(long) == 8) else if (sizeof(long) == 8 && !R4000_WAR)
__asm__("dmultu\t%2, %3" __asm__("dmultu\t%2, %3"
: "=h" (usecs), "=l" (lo) : "=h" (usecs), "=l" (lo)
: "r" (usecs), "r" (lpj) : "r" (usecs), "r" (lpj)
: GCC_REG_ACCUM); : GCC_REG_ACCUM);
else if (sizeof(long) == 8 && R4000_WAR)
__asm__("dmultu\t%3, %4\n\tmfhi\t%0"
: "=r" (usecs), "=h" (hi), "=l" (lo)
: "r" (usecs), "r" (lpj)
: GCC_REG_ACCUM);
__delay(usecs); __delay(usecs);
} }
......
...@@ -4,12 +4,74 @@ ...@@ -4,12 +4,74 @@
* for more details. * for more details.
* *
* Copyright (C) 2002, 2004, 2007 by Ralf Baechle * Copyright (C) 2002, 2004, 2007 by Ralf Baechle
* Copyright (C) 2007 Maciej W. Rozycki
*/ */
#ifndef _ASM_WAR_H #ifndef _ASM_WAR_H
#define _ASM_WAR_H #define _ASM_WAR_H
#include <war.h> #include <war.h>
/*
* Work around certain R4000 CPU errata (as implemented by GCC):
*
* - A double-word or a variable shift may give an incorrect result
* if executed immediately after starting an integer division:
* "MIPS R4000PC/SC Errata, Processor Revision 2.2 and 3.0",
* erratum #28
* "MIPS R4000MC Errata, Processor Revision 2.2 and 3.0", erratum
* #19
*
* - A double-word or a variable shift may give an incorrect result
* if executed while an integer multiplication is in progress:
* "MIPS R4000PC/SC Errata, Processor Revision 2.2 and 3.0",
* errata #16 & #28
*
* - An integer division may give an incorrect result if started in
* a delay slot of a taken branch or a jump:
* "MIPS R4000PC/SC Errata, Processor Revision 2.2 and 3.0",
* erratum #52
*/
#ifdef CONFIG_CPU_R4000_WORKAROUNDS
#define R4000_WAR 1
#else
#define R4000_WAR 0
#endif
/*
* Work around certain R4400 CPU errata (as implemented by GCC):
*
* - A double-word or a variable shift may give an incorrect result
* if executed immediately after starting an integer division:
* "MIPS R4400MC Errata, Processor Revision 1.0", erratum #10
* "MIPS R4400MC Errata, Processor Revision 2.0 & 3.0", erratum #4
*/
#ifdef CONFIG_CPU_R4400_WORKAROUNDS
#define R4400_WAR 1
#else
#define R4400_WAR 0
#endif
/*
* Work around the "daddi" and "daddiu" CPU errata:
*
* - The `daddi' instruction fails to trap on overflow.
* "MIPS R4000PC/SC Errata, Processor Revision 2.2 and 3.0",
* erratum #23
*
* - The `daddiu' instruction can produce an incorrect result.
* "MIPS R4000PC/SC Errata, Processor Revision 2.2 and 3.0",
* erratum #41
* "MIPS R4000MC Errata, Processor Revision 2.2 and 3.0", erratum
* #15
* "MIPS R4400PC/SC Errata, Processor Revision 1.0", erratum #7
* "MIPS R4400MC Errata, Processor Revision 1.0", erratum #5
*/
#ifdef CONFIG_CPU_DADDI_WORKAROUNDS
#define DADDI_WAR 1
#else
#define DADDI_WAR 0
#endif
/* /*
* Another R4600 erratum. Due to the lack of errata information the exact * Another R4600 erratum. Due to the lack of errata information the exact
* technical details aren't known. I've experimentally found that disabling * technical details aren't known. I've experimentally found that disabling
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment