Skip to content

Commit

Permalink
[MIPS] R4000/R4400 errata workarounds
Browse files Browse the repository at this point in the history
 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: Maciej W. Rozycki <macro@linux-mips.org>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
  • Loading branch information
Maciej W. Rozycki authored and Ralf Baechle committed Jan 29, 2008
1 parent 2b22c03 commit 20d60d9
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 30 deletions.
16 changes: 16 additions & 0 deletions arch/mips/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ config MACH_DECSTATION
select BOOT_ELF32
select CEVT_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 NO_IOPORT
select IRQ_CPU
Expand Down Expand Up @@ -1605,6 +1608,19 @@ config CPU_HAS_SYNC
config GENERIC_CLOCKEVENTS_BROADCAST
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/:
#
Expand Down
12 changes: 9 additions & 3 deletions arch/mips/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ cflags-$(CONFIG_CPU_R8000) += -march=r8000 -Wa,--trap
cflags-$(CONFIG_CPU_R10000) += $(call cc-option,-march=r10000,-march=r8000) \
-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_SB1_PASS_1_WORKAROUNDS
MODFLAGS += -msb1-pass1-workarounds
Expand Down Expand Up @@ -602,9 +606,11 @@ ifdef CONFIG_64BIT
endif
endif

ifeq ($(KBUILD_SYM32), y)
ifeq ($(call cc-option-yn,-msym32), y)
cflags-y += -msym32 -DKBUILD_64BIT_SYM32
ifeq ($(KBUILD_SYM32)$(call cc-option-yn,-msym32), yy)
cflags-y += -msym32 -DKBUILD_64BIT_SYM32
else
ifeq ($(CONFIG_CPU_DADDI_WORKAROUNDS), y)
$(error CONFIG_CPU_DADDI_WORKAROUNDS unsupported without -msym32)
endif
endif
endif
Expand Down
47 changes: 23 additions & 24 deletions arch/mips/kernel/cpu-bugs64.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
#include <asm/mipsregs.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)
{
asm volatile(
Expand Down Expand Up @@ -155,13 +164,7 @@ static inline void check_mult_sh(void)
}

printk("no.\n");
panic("Reliable operation impossible!\n"
#ifndef CONFIG_CPU_R4000
"Configure for R4000 to enable the workaround."
#else
"Please report to <linux-mips@linux-mips.org>."
#endif
);
panic(bug64hit, !R4000_WAR ? r4kwar : nowar);
}

static volatile int daddi_ov __initdata = 0;
Expand Down Expand Up @@ -233,15 +236,11 @@ static inline void check_daddi(void)
}

printk("no.\n");
panic("Reliable operation impossible!\n"
#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
);
panic(bug64hit, !DADDI_WAR ? daddiwar : nowar);
}

int daddiu_bug __initdata = -1;

static inline void check_daddiu(void)
{
long v, w, tmp;
Expand Down Expand Up @@ -281,7 +280,9 @@ static inline void check_daddiu(void)
: "=&r" (v), "=&r" (w), "=&r" (tmp)
: "I" (0xffffffffffffdb9aUL), "I" (0x1234));

if (v == w) {
daddiu_bug = v != w;

if (!daddiu_bug) {
printk("no.\n");
return;
}
Expand All @@ -303,18 +304,16 @@ static inline void check_daddiu(void)
}

printk("no.\n");
panic("Reliable operation impossible!\n"
#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
);
panic(bug64hit, !DADDI_WAR ? daddiwar : nowar);
}

void __init check_bugs64(void)
void __init check_bugs64_early(void)
{
check_mult_sh();
check_daddi();
check_daddiu();
}

void __init check_bugs64(void)
{
check_daddi();
}
4 changes: 3 additions & 1 deletion arch/mips/kernel/setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Copyright (C) 1994, 95, 96, 97, 98, 99, 2000, 01, 02, 03 Ralf Baechle
* Copyright (C) 1996 Stoned Elipot
* 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/ioport.h>
Expand All @@ -24,6 +24,7 @@

#include <asm/addrspace.h>
#include <asm/bootinfo.h>
#include <asm/bugs.h>
#include <asm/cache.h>
#include <asm/cpu.h>
#include <asm/sections.h>
Expand Down Expand Up @@ -561,6 +562,7 @@ void __init setup_arch(char **cmdline_p)
}
#endif
cpu_report();
check_bugs_early();

#if defined(CONFIG_VT)
#if defined(CONFIG_VGA_CONSOLE)
Expand Down
25 changes: 25 additions & 0 deletions include/asm-mips/bugs.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
/*
* This is included by init/main.c to check for architecture-dependent bugs.
*
* Copyright (C) 2007 Maciej W. Rozycki
*
* Needs:
* void check_bugs(void);
*/
#ifndef _ASM_BUGS_H
#define _ASM_BUGS_H

#include <linux/bug.h>
#include <linux/delay.h>

#include <asm/cpu.h>
#include <asm/cpu-info.h>

extern int daddiu_bug;

extern void check_bugs64_early(void);

extern void check_bugs32(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)
{
unsigned int cpu = smp_processor_id();
Expand All @@ -25,4 +40,14 @@ static inline void check_bugs(void)
#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 */
12 changes: 10 additions & 2 deletions include/asm-mips/delay.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
* Copyright (C) 1994 by Waldorf Electronics
* Copyright (C) 1995 - 2000, 01, 03 by Ralf Baechle
* Copyright (C) 1999, 2000 Silicon Graphics, Inc.
* Copyright (C) 2007 Maciej W. Rozycki
*/
#ifndef _ASM_DELAY_H
#define _ASM_DELAY_H

#include <linux/param.h>
#include <linux/smp.h>

#include <asm/compiler.h>
#include <asm/war.h>

static inline void __delay(unsigned long loops)
{
Expand Down Expand Up @@ -50,7 +53,7 @@ static inline void __delay(unsigned long loops)

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
Expand All @@ -70,11 +73,16 @@ static inline void __udelay(unsigned long usecs, unsigned long lpj)
: "=h" (usecs), "=l" (lo)
: "r" (usecs), "r" (lpj)
: GCC_REG_ACCUM);
else if (sizeof(long) == 8)
else if (sizeof(long) == 8 && !R4000_WAR)
__asm__("dmultu\t%2, %3"
: "=h" (usecs), "=l" (lo)
: "r" (usecs), "r" (lpj)
: 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);
}
Expand Down
62 changes: 62 additions & 0 deletions include/asm-mips/war.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,74 @@
* for more details.
*
* Copyright (C) 2002, 2004, 2007 by Ralf Baechle
* Copyright (C) 2007 Maciej W. Rozycki
*/
#ifndef _ASM_WAR_H
#define _ASM_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
* technical details aren't known. I've experimentally found that disabling
Expand Down

0 comments on commit 20d60d9

Please sign in to comment.