[PATCHv3,MIPS] Add support for O32 FPXX and program header based ABI information

Message ID 6D39441BF12EF246A7ABCE6654B0235320F2BEE7@LEMAIL01.le.imgtec.org
State Superseded
Headers

Commit Message

Matthew Fortune Oct. 17, 2014, 8:37 p.m. UTC
  Hi Joseph,

Your request to allow pre-PT_MIPS_ABIFLAGS binaries with new soft-float
or double-precision made the implementation so ugly that I could barely
read it so I've rewritten it entirely. It's a good thing overall as the
new version is cleaner and also handles some more compatibility issues.

There are several things which have changed for compatibility/safety:

1) pre-PT_MIPS_ABIFLAGS will link with double/single/soft float. (The
   new implementation makes it easy to handle single float so it does
   despite it not being a glibc supported ABI).

2) When running a new glibc on an old kernel then the dynamic linker
   will detect if the kernel loaded an FP64 or FP64A binary (or
   dynamic linker) and refuse to load any shared libraries. This
   will help protect users from running binaries which will suffer
   strange floating-point crashes if allowed to execute in the wrong
   floating-point mode.

3) Future proofing the .MIPS.abiflags flags2 field. No bits are
   currently defined for this field so LD refuses to link any object
   with a non-zero flags2 to allow special rules to be implemented
   when bits are defined. Ld.so will do the same thing and reject
   anything with a non-zero flags2 field.

4) Prevent loading objects with unknown ASEs. Also reject MSA in the
   absence of new HWCAP bits. Other ASEs are not rejected despite
   the lack of HWCAPs as they have historically been loaded
   unconditionally

5) Apply checks as listed in 3,4 to the already loaded objects which
   allows the link to fail if an application uses a new ASE or a new
   feature marked using a flags2 bit.

Do all those checks seem acceptable to you?

The only bit which feels a little weird to me is that when ld.so
refuses to load objects because of an application's .MIPS.abiflags
(i.e. application uses MSA or has a flags2 bit set) then the error
is reported as:

"error while loading shared libraries: <blah>: cannot open shared
object file: No such file or directory"

Even though the issue is not strictly to do with the library being
loaded. However, The LD_DEBUG output gives an accurate message as
to why this happened.

The only way to improve that would be to perform checks earlier
on the objects which are loaded by the kernel. I.e. another hook
to allow inspection within the startup of ld.so.

All feedback appreciated.

Thanks,
Matthew

2014-09-30  Matthew Fortune  <matthew.fortune@imgtec.com>

	* elf/elf.h (PT_MIPS_ABIFLAGS): Define.
	(Elf_MIPS_ABIFlags_v0): New structure.
	(EF_MIPS_FP64): Define.
	(MIPS_AFL_REG_NONE, MIPS_AFL_REG_32, MIPS_AFL_REG_64): Likewise.
	(MIPS_AFL_REG_128, MIPS_AFL_ASE_DSP, MIPS_AFL_ASE_DSP64): Likewise.
	(MIPS_AFL_ASE_DSPR2, MIPS_AFL_ASE_EVA, MIPS_AFL_ASE_MCU): Likewise.
	(MIPS_AFL_ASE_MDMX, MIPS_AFL_ASE_MIPS3D, MIPS_AFL_ASE_MT): Likewise.
	(MIPS_AFL_ASE_SMARTMIPS, MIPS_AFL_ASE_VIRT): Likewise.
	(MIPS_AFL_ASE_VIRT64, MIPS_AFL_ASE_MSA, MIPS_AFL_ASE_MSA64): Likewise.
	(MIPS_AFL_ASE_MIPS16, MIPS_AFL_ASE_MICROMIPS): Likewise.
	(MIPS_AFL_ASE_XPA, MIPS_AFL_EXT_XLR, MIPS_AFL_EXT_OCTEON2): Likewise.
	(MIPS_AFL_EXT_OCTEONP, MIPS_AFL_EXT_LOONGSON_3A): Likewise.
	(MIPS_AFL_EXT_OCTEON, MIPS_AFL_EXT_5900, MIPS_AFL_EXT_4010): Likewise.
	(MIPS_AFL_EXT_4100, MIPS_AFL_EXT_3900, MIPS_AFL_EXT_10000): Likewise.
	(MIPS_AFL_EXT_SB1, MIPS_AFL_EXT_4111, MIPS_AFL_EXT_4120): Likewise.
	(MIPS_AFL_EXT_5400, MIPS_AFL_EXT_5500): Likewise.
	(MIPS_AFL_EXT_LOONGSON_2E, MIPS_AFL_EXT_LOONGSON_2F): Likewise.
	(Val_GNU_MIPS_ABI_FP_ANY, Val_GNU_MIPS_ABI_FP_DOUBLE): New enum values.
	(Val_GNU_MIPS_ABI_FP_SINGLE, Val_GNU_MIPS_ABI_FP_SOFT): Likewise.
	(Val_GNU_MIPS_ABI_FP_OLD_64, Val_GNU_MIPS_ABI_FP_XX): Likewise.
	(Val_GNU_MIPS_ABI_FP_64, Val_GNU_MIPS_ABI_FP_64A): Likewise.
	* libc-abis: Add new MIPS_O32_FP64 feature.
	* sysdeps/mips/Makefile [subdir=elf]: Add tst-abi-interlink,
	tst-mode-switch-1, tst-mode-switch-2, tst-mode-switch-3 tests.
	* sysdeps/mips/bits/linkmap.h (struct link_map_machine): Add fpmode
	field.
	* sysdeps/mips/dl-machine.h (elf_machine_matches_host): Reject
	EF_MIPS_FP64.
	* sysdeps/mips/dl-machine-reject-phdr.h: New file.
	* sysdeps/mips/tst-abi-fp32mod.c: Likewise.
	* sysdeps/mips/tst-abi-fpxxmod.c: Likewise.
	* sysdeps/mips/tst-abi-fpxxomod.c: Likewise.
	* sysdeps/mips/tst-abi-fp64mod.c: Likewise.
	* sysdeps/mips/tst-abi-fp64amod.c: Likewise.
	* sysdeps/mips/tst-abi-interlink.c: Likewise.
	* sysdeps/mips/tst-mode-switch-1.c: Likewise.
	* sysdeps/mips/tst-mode-switch-2.c: Likewise.
	* sysdeps/mips/tst-mode-switch-3.c: Likewise.
	* sysdeps/unix/sysv/linux/mips/configure.ac (o32-fpabi): Define to
	record the current FP ABI extension.
	(mips-mode-switch): Define to show if kernel headers support mode
	switching.
	* sysdeps/unix/sysv/linux/mips/configure: Regenerate.
	* sysdeps/unix/sysv/linux/mips/ldsodefs.h: Increase maximum
	supported SYSV ABI version to 3.
---
 elf/elf.h                                 | 100 +++-
 libc-abis                                 |   3 +
 sysdeps/mips/Makefile                     |  50 ++
 sysdeps/mips/bits/linkmap.h               |   2 +
 sysdeps/mips/dl-machine-reject-phdr.h     | 355 +++++++++++++
 sysdeps/mips/dl-machine.h                 |   5 +
 sysdeps/mips/tst-abi-fp32mod.c            |  22 +
 sysdeps/mips/tst-abi-fp64amod.c           |  22 +
 sysdeps/mips/tst-abi-fp64mod.c            |  22 +
 sysdeps/mips/tst-abi-fpxxmod.c            |  22 +
 sysdeps/mips/tst-abi-fpxxomod.c           |  22 +
 sysdeps/mips/tst-abi-interlink.c          | 844 ++++++++++++++++++++++++++++++
 sysdeps/mips/tst-mode-switch-1.c          | 120 +++++
 sysdeps/mips/tst-mode-switch-2.c          | 160 ++++++
 sysdeps/mips/tst-mode-switch-3.c          |  87 +++
 sysdeps/unix/sysv/linux/mips/configure    | 142 +++++
 sysdeps/unix/sysv/linux/mips/configure.ac |  53 ++
 sysdeps/unix/sysv/linux/mips/ldsodefs.h   |   2 +-
 18 files changed, 2029 insertions(+), 4 deletions(-)
 create mode 100644 sysdeps/mips/dl-machine-reject-phdr.h
 create mode 100644 sysdeps/mips/tst-abi-fp32mod.c
 create mode 100644 sysdeps/mips/tst-abi-fp64amod.c
 create mode 100644 sysdeps/mips/tst-abi-fp64mod.c
 create mode 100644 sysdeps/mips/tst-abi-fpxxmod.c
 create mode 100644 sysdeps/mips/tst-abi-fpxxomod.c
 create mode 100644 sysdeps/mips/tst-abi-interlink.c
 create mode 100644 sysdeps/mips/tst-mode-switch-1.c
 create mode 100644 sysdeps/mips/tst-mode-switch-2.c
 create mode 100644 sysdeps/mips/tst-mode-switch-3.c
  

Comments

Joseph Myers Oct. 17, 2014, 8:52 p.m. UTC | #1
On Fri, 17 Oct 2014, Matthew Fortune wrote:

> 4) Prevent loading objects with unknown ASEs. Also reject MSA in the
>    absence of new HWCAP bits. Other ASEs are not rejected despite
>    the lack of HWCAPs as they have historically been loaded
>    unconditionally

Given the deferrment of the HWCAP addition until the values are in the 
kernel, this needs to be taken out to be considered later once HWCAPs are 
in.  (More generally, please make clear what the patch depends on that 
isn't in current glibc master, and make sure to ping any such patches that 
haven't been reviewed.  I think you're depending on a libc-abis patch, at 
least.)
  
Matthew Fortune Oct. 19, 2014, 7:30 a.m. UTC | #2
Joseph S. Myers <joseph@codesourcery.com> writes:
> On Fri, 17 Oct 2014, Matthew Fortune wrote:
> 
> > 4) Prevent loading objects with unknown ASEs. Also reject MSA in the
> >    absence of new HWCAP bits. Other ASEs are not rejected despite
> >    the lack of HWCAPs as they have historically been loaded
> >    unconditionally
> 
> Given the deferrment of the HWCAP addition until the values are in the
> kernel, this needs to be taken out to be considered later once HWCAPs are
> in.  (More generally, please make clear what the patch depends on that
> isn't in current glibc master, and make sure to ping any such patches that
> haven't been reviewed.  I think you're depending on a libc-abis patch, at
> least.)

The patch is not dependent on the HWCAP patch.  I have hardcoded the logic
to assume that the HWCAPs are never set.  This simply means that any ASE
which was defined before MSA is accepted and anything from MSA to any
future ASE is rejected.

The patch is dependent on...

Create a hook for inspecting program headers during library load. Roland has
said OK to this but I am holding off getting it committed until this patch
is OK just in case something has to change in the hook:
http://sourceware.org/ml/libc-alpha/2014-10/msg00352.html

Moving MIPS libc-abis to the top-level (but this dependency can easily be
removed if removing the file causes problems).
http://sourceware.org/ml/libc-alpha/2014-10/msg00018.html

Thanks,
Matthew
  
Joseph Myers Oct. 19, 2014, 3:43 p.m. UTC | #3
On Sun, 19 Oct 2014, Matthew Fortune wrote:

> The patch is not dependent on the HWCAP patch.  I have hardcoded the logic

Where does the definition of HWCAP_MIPS_MSA that this patch uses come 
from?

> to assume that the HWCAPs are never set.  This simply means that any ASE
> which was defined before MSA is accepted and anything from MSA to any
> future ASE is rejected.

Did we actually reach an architecture-independent consensus on the 
desirability or otherwise of such checks for future ISA extensions (a 
whole-toolchain issue, not just a glibc one)?  Did the 
architecture-independent discussion on this even get started?
  

Patch

diff --git a/elf/elf.h b/elf/elf.h
index 78815e8..b80fae3 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -1383,6 +1383,7 @@  typedef struct
 #define EF_MIPS_64BIT_WHIRL	16
 #define EF_MIPS_ABI2		32
 #define EF_MIPS_ABI_ON32	64
+#define EF_MIPS_FP64		512  /* Uses FP64 (12 callee-saved).  */
 #define EF_MIPS_NAN2008	1024  /* Uses IEEE 754-2008 NaN encoding.  */
 #define EF_MIPS_ARCH		0xf0000000 /* MIPS architecture level.  */
 
@@ -1631,9 +1632,10 @@  typedef struct
 
 /* Legal values for p_type field of Elf32_Phdr.  */
 
-#define PT_MIPS_REGINFO	0x70000000	/* Register usage information */
-#define PT_MIPS_RTPROC  0x70000001	/* Runtime procedure table. */
-#define PT_MIPS_OPTIONS 0x70000002
+#define PT_MIPS_REGINFO	  0x70000000	/* Register usage information. */
+#define PT_MIPS_RTPROC	  0x70000001	/* Runtime procedure table. */
+#define PT_MIPS_OPTIONS	  0x70000002
+#define PT_MIPS_ABIFLAGS  0x70000003	/* FP mode requirement. */
 
 /* Special program header types.  */
 
@@ -1755,6 +1757,98 @@  typedef struct
 
 typedef Elf32_Addr Elf32_Conflict;
 
+typedef struct
+{
+  /* Version of flags structure.  */
+  Elf32_Half version;
+  /* The level of the ISA: 1-5, 32, 64.  */
+  unsigned char isa_level;
+  /* The revision of ISA: 0 for MIPS V and below, 1-n otherwise.  */
+  unsigned char isa_rev;
+  /* The size of general purpose registers.  */
+  unsigned char gpr_size;
+  /* The size of co-processor 1 registers.  */
+  unsigned char cpr1_size;
+  /* The size of co-processor 2 registers.  */
+  unsigned char cpr2_size;
+  /* The floating-point ABI.  */
+  unsigned char fp_abi;
+  /* Processor-specific extension.  */
+  Elf32_Word isa_ext;
+  /* Mask of ASEs used.  */
+  Elf32_Word ases;
+  /* Mask of general flags.  */
+  Elf32_Word flags1;
+  Elf32_Word flags2;
+} Elf_MIPS_ABIFlags_v0;
+
+/* Values for the register size bytes of an abi flags structure.  */
+
+#define MIPS_AFL_REG_NONE	0x00	 /* No registers.  */
+#define MIPS_AFL_REG_32		0x01	 /* 32-bit registers.  */
+#define MIPS_AFL_REG_64		0x02	 /* 64-bit registers.  */
+#define MIPS_AFL_REG_128	0x03	 /* 128-bit registers.  */
+
+/* Masks for the ases word of an ABI flags structure.  */
+
+#define MIPS_AFL_ASE_DSP	0x00000001 /* DSP ASE.  */
+#define MIPS_AFL_ASE_DSPR2	0x00000002 /* DSP R2 ASE.  */
+#define MIPS_AFL_ASE_EVA	0x00000004 /* Enhanced VA Scheme.  */
+#define MIPS_AFL_ASE_MCU	0x00000008 /* MCU (MicroController) ASE.  */
+#define MIPS_AFL_ASE_MDMX	0x00000010 /* MDMX ASE.  */
+#define MIPS_AFL_ASE_MIPS3D	0x00000020 /* MIPS-3D ASE.  */
+#define MIPS_AFL_ASE_MT		0x00000040 /* MT ASE.  */
+#define MIPS_AFL_ASE_SMARTMIPS	0x00000080 /* SmartMIPS ASE.  */
+#define MIPS_AFL_ASE_VIRT	0x00000100 /* VZ ASE.  */
+#define MIPS_AFL_ASE_MSA	0x00000200 /* MSA ASE.  */
+#define MIPS_AFL_ASE_MIPS16	0x00000400 /* MIPS16 ASE.  */
+#define MIPS_AFL_ASE_MICROMIPS	0x00000800 /* MICROMIPS ASE.  */
+#define MIPS_AFL_ASE_XPA	0x00001000 /* XPA ASE.  */
+
+/* Values for the isa_ext word of an ABI flags structure.  */
+
+#define MIPS_AFL_EXT_XLR	  1   /* RMI Xlr instruction.  */
+#define MIPS_AFL_EXT_OCTEON2	  2   /* Cavium Networks Octeon2.  */
+#define MIPS_AFL_EXT_OCTEONP	  3   /* Cavium Networks OcteonP.  */
+#define MIPS_AFL_EXT_LOONGSON_3A  4   /* Loongson 3A.  */
+#define MIPS_AFL_EXT_OCTEON	  5   /* Cavium Networks Octeon.  */
+#define MIPS_AFL_EXT_5900	  6   /* MIPS R5900 instruction.  */
+#define MIPS_AFL_EXT_4650	  7   /* MIPS R4650 instruction.  */
+#define MIPS_AFL_EXT_4010	  8   /* LSI R4010 instruction.  */
+#define MIPS_AFL_EXT_4100	  9   /* NEC VR4100 instruction.  */
+#define MIPS_AFL_EXT_3900	  10  /* Toshiba R3900 instruction.  */
+#define MIPS_AFL_EXT_10000	  11  /* MIPS R10000 instruction.  */
+#define MIPS_AFL_EXT_SB1	  12  /* Broadcom SB-1 instruction.  */
+#define MIPS_AFL_EXT_4111	  13  /* NEC VR4111/VR4181 instruction.  */
+#define MIPS_AFL_EXT_4120	  14  /* NEC VR4120 instruction.  */
+#define MIPS_AFL_EXT_5400	  15  /* NEC VR5400 instruction.  */
+#define MIPS_AFL_EXT_5500	  16  /* NEC VR5500 instruction.  */
+#define MIPS_AFL_EXT_LOONGSON_2E  17  /* ST Microelectronics Loongson 2E.  */
+#define MIPS_AFL_EXT_LOONGSON_2F  18  /* ST Microelectronics Loongson 2F.  */
+
+/* Masks for the flags1 word of an ABI flags structure.  */
+#define MIPS_AFL_FLAGS1_ODDSPREG  1  /* Uses odd single-precision registers.  */
+
+/* Object attribute values.  */
+enum
+{
+  /* Not tagged or not using any ABIs affected by the differences.  */
+  Val_GNU_MIPS_ABI_FP_ANY = 0,
+  /* Using hard-float -mdouble-float.  */
+  Val_GNU_MIPS_ABI_FP_DOUBLE = 1,
+  /* Using hard-float -msingle-float.  */
+  Val_GNU_MIPS_ABI_FP_SINGLE = 2,
+  /* Using soft-float.  */
+  Val_GNU_MIPS_ABI_FP_SOFT = 3,
+  /* Using -mips32r2 -mfp64.  */
+  Val_GNU_MIPS_ABI_FP_OLD_64 = 4,
+  /* Using -mfpxx.  */
+  Val_GNU_MIPS_ABI_FP_XX = 5,
+  /* Using -mips32r2 -mfp64.  */
+  Val_GNU_MIPS_ABI_FP_64 = 6,
+  /* Using -mips32r2 -mfp64 -mno-odd-spreg.  */
+  Val_GNU_MIPS_ABI_FP_64A = 7
+};
 
 /* HPPA specific definitions.  */
 
diff --git a/libc-abis b/libc-abis
index c9c24a0..84efb1e 100644
--- a/libc-abis
+++ b/libc-abis
@@ -49,3 +49,6 @@  IFUNC		powerpc64-*-linux*
 IFUNC		powerpc-*-linux*
 IFUNC		sparc64-*-linux*
 IFUNC		sparc-*-linux*
+#
+# MIPS O32 FP64
+MIPS_O32_FP64	mips*-*-linux*
diff --git a/sysdeps/mips/Makefile b/sysdeps/mips/Makefile
index a152699..463b121 100644
--- a/sysdeps/mips/Makefile
+++ b/sysdeps/mips/Makefile
@@ -26,3 +26,53 @@  CPPFLAGS-crtn.S += $(pic-ccflag)
 endif
 
 ASFLAGS-.os += $(pic-ccflag)
+
+ifeq ($(subdir),elf)
+ifneq ($(o32-fpabi),)
+tests += tst-abi-interlink
+
+fpabi-modules-names =
+fpabi_list =
+ifneq (,$(filter $(o32-fpabi),32 xx xxo))
+fpabi-modules-names += tst-abi-fp32mod
+CFLAGS-tst-abi-fp32mod.c += -mfp32
+endif
+ifneq (,$(filter $(o32-fpabi),xx))
+fpabi-modules-names += tst-abi-fpxxmod
+CFLAGS-tst-abi-fpxxmod.c += -mfpxx -mno-odd-spreg
+endif
+ifneq (,$(filter $(o32-fpabi),xx xxo))
+fpabi-modules-names += tst-abi-fpxxomod
+CFLAGS-tst-abi-fpxxomod.c += -mfpxx -modd-spreg
+endif
+ifneq (,$(filter $(o32-fpabi),xx 64a))
+fpabi-modules-names += tst-abi-fp64amod
+CFLAGS-tst-abi-fp64amod.c += -mfp64 -mno-odd-spreg
+endif
+ifneq (,$(filter $(o32-fpabi),xx xxo 64a 64))
+fpabi-modules-names += tst-abi-fp64mod
+CFLAGS-tst-abi-fp64mod.c += -mfp64 -modd-spreg
+endif
+modules-names += $(fpabi-modules-names)
+
+comma:=,
+empty:=
+space:=$(empty) $(empty)
+fpabi_list=$(subst $(space),$(comma),$(patsubst tst-abi-%mod,o_%,\
+				     $(fpabi-modules-names)))
+CPPFLAGS-tst-abi-interlink.c += -DFPABI_LIST=$(fpabi_list)
+CPPFLAGS-tst-abi-interlink.c += -DFPABI_COUNT=$(words $(fpabi-modules-names))
+CPPFLAGS-tst-abi-interlink.c += -DFPABI_NATIVE=o_fp$(o32-fpabi)
+$(objpfx)tst-abi-interlink: $(libdl)
+$(objpfx)tst-abi-interlink.out: $(patsubst %,$(objpfx)%.so,\
+					   $(fpabi-modules-names))
+endif
+
+ifeq ($(mips-mode-switch),yes)
+ifeq ($(o32-fpabi),xx)
+tests += tst-mode-switch-1 tst-mode-switch-2 tst-mode-switch-3
+$(objpfx)tst-mode-switch-1: $(shared-thread-library)
+$(objpfx)tst-mode-switch-2: $(shared-thread-library)
+endif
+endif
+endif
diff --git a/sysdeps/mips/bits/linkmap.h b/sysdeps/mips/bits/linkmap.h
index a6df782..1fb9678 100644
--- a/sysdeps/mips/bits/linkmap.h
+++ b/sysdeps/mips/bits/linkmap.h
@@ -1,4 +1,6 @@ 
 struct link_map_machine
   {
     ElfW(Addr) plt; /* Address of .plt */
+    ElfW(Word) fpabi; /* FP ABI of the object */
+    unsigned int odd_spreg; /* Does the object require odd_spreg support? */
   };
diff --git a/sysdeps/mips/dl-machine-reject-phdr.h b/sysdeps/mips/dl-machine-reject-phdr.h
new file mode 100644
index 0000000..fb98033
--- /dev/null
+++ b/sysdeps/mips/dl-machine-reject-phdr.h
@@ -0,0 +1,355 @@ 
+/* Machine-dependent program header inspection for the ELF loader.
+   Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_MACHINE_REJECT_PHDR_H
+#define _DL_MACHINE_REJECT_PHDR_H 1
+
+#include <unistd.h>
+#include <sys/prctl.h>
+
+#if defined PR_GET_FP_MODE && defined PR_SET_FP_MODE
+# define HAVE_PRCTL_FP_MODE 1
+#else
+# define HAVE_PRCTL_FP_MODE 0
+#endif
+
+/* Search the program headers for the ABI Flags.  */
+
+static inline const ElfW(Phdr) *
+find_mips_abiflags (const ElfW(Phdr) *phdr, ElfW(Half) phnum)
+{
+  const ElfW(Phdr) *ph;
+
+  for (ph = phdr; ph < &phdr[phnum]; ++ph)
+    if (ph->p_type == PT_MIPS_ABIFLAGS)
+      return ph;
+  return NULL;
+}
+
+/* Cache the FP ABI value from the PT_MIPS_ABIFLAGS program header.  */
+
+static void
+mips_cache_fpabi (struct link_map *l)
+{
+  if (l->l_mach.fpabi == 0)
+    {
+      const ElfW(Phdr) *ph = find_mips_abiflags (l->l_phdr, l->l_phnum);
+      l->l_mach.fpabi = -1;
+      l->l_mach.odd_spreg = true;
+
+      if (ph)
+	{
+	  Elf_MIPS_ABIFlags_v0 * mips_abiflags;
+	  if (ph->p_filesz < sizeof (Elf_MIPS_ABIFlags_v0))
+	    {
+	      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+		_dl_debug_printf ("   malformed PT_MIPS_ABIFLAGS found\n");
+	      return true;
+	    }
+
+	  mips_abiflags = (Elf_MIPS_ABIFlags_v0 *) (l->l_addr + ph->p_vaddr);
+	  l->l_mach.fpabi = mips_abiflags->fp_abi;
+	  l->l_mach.odd_spreg = (mips_abiflags->flags1
+				 & MIPS_AFL_FLAGS1_ODDSPREG) != 0;
+
+	  if (__glibc_unlikely (mips_abiflags->flags2 != 0))
+	    {
+	      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+		_dl_debug_printf ("   unknown MIPS.abiflags flags2: %u\n",
+				  mips_abiflags->flags2);
+	      return true;
+	    }
+	}
+    }
+}
+
+/* Return a description of the specified floating-point ABI.  */
+
+static const char *
+mips_fp_abi_string (int fpabi)
+{
+  switch (fpabi)
+    {
+    case Val_GNU_MIPS_ABI_FP_ANY:
+      return "Hard or soft float";
+    case Val_GNU_MIPS_ABI_FP_DOUBLE:
+      return "Hard float (double precision)";
+    case Val_GNU_MIPS_ABI_FP_SINGLE:
+      return "Hard float (single precision)";
+    case Val_GNU_MIPS_ABI_FP_SOFT:
+      return "Soft float";
+    case Val_GNU_MIPS_ABI_FP_OLD_64:
+      return "Unsupported FP64";
+    case Val_GNU_MIPS_ABI_FP_XX:
+      return "Hard float (32-bit CPU, Any FPU)";
+    case Val_GNU_MIPS_ABI_FP_64:
+      return "Hard float (32-bit CPU, 64-bit FPU)";
+    case Val_GNU_MIPS_ABI_FP_64A:
+      return "Hard float compat (32-bit CPU, 64-bit FPU)";
+    case -1:
+      return "Double precision, single precision or soft";
+    default:
+      return "Unknown FP ABI";
+    }
+}
+
+/* A structure to describe the requirements of each FP ABI extension.
+   Each field says whether the ABI can be executed in that mode.  The FR0 field
+   is actually overloaded and means 'default' FR mode for the ABI.  I.e. For
+   O32 it is FR0 and for N32/N64 it is actually FR1.  Since this logic is
+   focussed on the intricacies of mode management for O32 we call the field
+   FR0.  */
+
+struct abi_req
+{
+  bool single;
+  bool soft;
+  bool fr0;
+  bool fr1;
+  bool fre;
+};
+
+/* FP ABI requirements for all Val_GNU_MIPS_ABI_FP_* values.  */
+
+static const struct abi_req reqs[8] = {{true,  true,  true,  true,  true},
+				       {false, false, true,  false, true},
+				       {true,  false, false, false, false},
+				       {false, true,  false, false, false},
+				       {false, false, false, false, false},
+				       {false, false, true,  true,  true},
+				       {false, false, false, true,  false},
+				       {false, false, false, true,  true}};
+
+/* FP ABI requirements for objects without a PT_MIPS_ABIFLAGS segment.  */
+
+static const struct abi_req none_req = { true, true, true, false, true };
+
+/* Return true iff ELF program headers are incompatible with the running
+   host.  This verifies that floating-point ABIs are compatible and
+   re-configures the hardware mode if necessary.  This code handles both the
+   DT_NEEDED libraries and the dlopen'ed libraries.  It also accounts for the
+   impact of dlclose.  */
+
+static bool __attribute_used__
+elf_machine_reject_phdr_p (const ElfW(Phdr) *phdr, uint_fast16_t phnum,
+			   const char *buf, size_t len, struct link_map *map,
+			   int fd)
+{
+  const ElfW(Phdr) *ph = find_mips_abiflags (phdr, phnum);
+  struct link_map *l;
+  Lmid_t nsid;
+  int in_abi = -1;
+  struct abi_req in_req;
+  Elf_MIPS_ABIFlags_v0 *mips_abiflags = NULL;
+  bool perfect_match = false;
+#if _MIPS_SIM == _ABIO32
+  unsigned int cur_mode = -1;
+# if HAVE_PRCTL_FP_MODE
+  bool cannot_mode_switch = false;
+
+  /* Get the current hardware mode.  */
+  cur_mode = __prctl (PR_GET_FP_MODE);
+# endif
+#endif
+
+  /* Read the attributes section.  */
+  if (ph != NULL)
+    {
+      ElfW(Addr) size = ph->p_filesz;
+
+      if (ph->p_offset + size <= len)
+	mips_abiflags = (Elf_MIPS_ABIFlags_v0 *) (buf + ph->p_offset);
+      else
+	{
+	  mips_abiflags = alloca (size);
+	  __lseek (fd, ph->p_offset, SEEK_SET);
+	  if (__libc_read (fd, (void *) mips_abiflags, size) != size)
+	    {
+	      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+		_dl_debug_printf ("   unable to read PT_MIPS_ABIFLAGS\n");
+	      return true;
+	    }
+	}
+      if (size < sizeof (Elf_MIPS_ABIFlags_v0))
+	{
+	  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	    _dl_debug_printf ("   contains malformed PT_MIPS_ABIFLAGS\n");
+	  return true;
+	}
+      if (__glibc_unlikely (mips_abiflags->flags2 != 0))
+	{
+	  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	    _dl_debug_printf ("   unknown MIPS.abiflags flags2: %u\n",
+			      mips_abiflags->flags2);
+	  return true;
+	}
+
+      /* Check for MSA support.  */
+      if ((mips_abiflags->ases & MIPS_AFL_ASE_MSA) != 0
+	  && (GLRO(dl_hwcap) & HWCAP_MIPS_MSA) == 0)
+	{
+	  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	    _dl_debug_printf ("   requires MSA support\n");
+	  return true;
+	}
+
+      in_abi = mips_abiflags->fp_abi;
+    }
+
+  /* ANY is compatible with anything.  */
+  perfect_match |= (in_abi == Val_GNU_MIPS_ABI_FP_ANY);
+
+  /* Unknown ABIs are rejected.  */
+  if (in_abi != -1 && in_abi > Val_GNU_MIPS_ABI_FP_64A)
+    {
+      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	_dl_debug_printf ("   uses unknown FP ABI: %u\n", in_abi);
+      return true;
+    }
+
+  /* Obtain the initial requirements.  */
+  in_req = (in_abi == -1) ? none_req : reqs[in_abi];
+
+  /* Check that the new requirement does not conflict with any currently
+     loaded object.  */
+  for (nsid = 0; nsid < DL_NNS; ++nsid)
+    for (l = GL(dl_ns)[nsid]._ns_loaded; l != NULL; l = l->l_next)
+      {
+	struct abi_req existing_req;
+
+	mips_cache_fpabi (l);
+
+#if _MIPS_SIM == _ABIO32
+	/* A special case arises for O32 FP64 and FP64A where the kernel
+	   pre-dates PT_MIPS_ABIFLAGS.  These ABIs will be blindly loaded even
+	   if the hardware mode is unavailable or disabled.  In this
+	   circumstance the prctl call to obtain the current mode will fail.
+	   Detect this situation here and reject everything.  This will
+	   effectively prevent dynamically loaded applications from failing in
+	   unusual ways but there is nothing we can do to help static
+	   applications.  */
+
+	if ((l->l_mach.fpabi == Val_GNU_MIPS_ABI_FP_64A
+	     || l->l_mach.fpabi == Val_GNU_MIPS_ABI_FP_64)
+	    && cur_mode == -1)
+	  {
+	    if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	      _dl_debug_printf ("   found %s running in the wrong mode\n",
+				mips_fp_abi_string (l->l_mach.fpabi));
+	    return true;
+	  }
+#endif
+
+	/* Found a perfect match, success.  */
+	perfect_match |= (in_abi == l->l_mach.fpabi);
+
+	/* Unknown ABIs are rejected.  */
+	if (l->l_mach.fpabi != -1 && l->l_mach.fpabi > Val_GNU_MIPS_ABI_FP_64A)
+	  {
+	    if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	      _dl_debug_printf ("   found unknown FP ABI: %u\n",
+				l->l_mach.fpabi);
+	    return true;
+	  }
+
+	existing_req = (l->l_mach.fpabi == -1 ? none_req
+			: reqs[l->l_mach.fpabi]);
+
+	/* Merge requirements.  */
+	in_req.soft &= existing_req.soft;
+	in_req.single &= existing_req.single;
+	in_req.fr0 &= existing_req.fr0;
+	in_req.fr1 &= existing_req.fr1;
+	in_req.fre &= existing_req.fre;
+
+	/* If there is at least one mode which is still usable then the new
+	   object can be loaded.  */
+	if (in_req.single || in_req.soft || in_req.fr1 || in_req.fr0
+	    || in_req.fre)
+	  {
+#if _MIPS_SIM == _ABIO32 && HAVE_PRCTL_FP_MODE
+	    /* Account for loaded ABIs which prohibit mode switching.  */
+	    if (l->l_mach.fpabi == Val_GNU_MIPS_ABI_FP_XX)
+	      cannot_mode_switch |= l->l_mach.odd_spreg;
+#endif
+	  }
+	else
+	  {
+	    if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	      _dl_debug_printf ("   uses %s, already loaded %s\n",
+				mips_fp_abi_string (in_abi),
+				mips_fp_abi_string (l->l_mach.fpabi));
+	    return true;
+	  }
+      }
+
+#if _MIPS_SIM == _ABIO32
+  /* At this point we know that the newly loaded object is compatible with all
+     existing objects but the hardware mode may not be correct.  */
+
+  if ((in_req.fr1 || in_req.fre || in_req.fr0)
+      && !perfect_match)
+    {
+      unsigned int fr1_mode = PR_FP_MODE_FR;
+
+      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	_dl_debug_printf ("   needs %s%s mode\n", in_req.fr0 ? "FR0 or " : "",
+			  (in_req.fre && !in_req.fr1) ? "FRE" : "FR1");
+
+      /* If the PR_GET_FP_MODE is not supported then only FR0 is available.
+	 If the overall requirements cannot be met by FR0 then reject the
+	 object.  */
+      if (cur_mode == -1)
+	return !in_req.fr0;
+
+# if HAVE_PRCTL_FP_MODE
+      /* It is not possible to change the mode of a thread which may be
+	 executing FPXX code with odd-singles.  If an FPXX object with
+	 odd-singles is loaded then just check the current mode is OK. This can
+	 be either the FR1 mode or FR0 if the requirements are met by FR0.  */
+      if (cannot_mode_switch)
+	return (!(in_req.fre && cur_mode == (PR_FP_MODE_FR | PR_FP_MODE_FRE))
+	        && !(in_req.fr1 && cur_mode == PR_FP_MODE_FR)
+		&& !(in_req.fr0 && cur_mode == 0));
+
+      /* If the overall requirements can be satisfied by FRE but not FR1 then
+	 fr1_mode must become FRE.  */
+      if (in_req.fre && !in_req.fr1)
+	fr1_mode |= PR_FP_MODE_FRE;
+
+      /* Set the new mode.  Use fr1_mode if the requirements cannot be met by
+	 FR0.  */
+      if (!in_req.fr0)
+	return __prctl (PR_SET_FP_MODE, fr1_mode) != 0;
+      else if (__prctl (PR_SET_FP_MODE, /* fr0_mode */ 0) != 0)
+	{
+	  /* Setting FR0 can validly fail on an R6 core so retry with the FR1
+	     mode as a fall back.  */
+	  if (errno != ENOTSUP)
+	    return true;
+
+	  return __prctl (PR_SET_FP_MODE, fr1_mode) != 0;
+	}
+# endif /* HAVE_PRCTL_FP_MODE */
+    }
+#endif /* _MIPS_SIM == _ABIO32 */
+
+  return false;
+}
+
+#endif /* dl-machine-reject-phdr.h */
diff --git a/sysdeps/mips/dl-machine.h b/sysdeps/mips/dl-machine.h
index eef0384..c05d6d4 100644
--- a/sysdeps/mips/dl-machine.h
+++ b/sysdeps/mips/dl-machine.h
@@ -99,6 +99,11 @@  elf_machine_matches_host (const ElfW(Ehdr) *ehdr)
   if ((ehdr->e_flags & EF_MIPS_NAN2008) != ELF_MACHINE_NAN2008)
     return 0;
 
+  /* Ensure that the old O32 FP64 ABI is never loaded, it is not supported
+     on linux.  */
+  if (ehdr->e_flags & EF_MIPS_FP64)
+    return 0;
+
   switch (ehdr->e_machine)
     {
     case EM_MIPS:
diff --git a/sysdeps/mips/tst-abi-fp32mod.c b/sysdeps/mips/tst-abi-fp32mod.c
new file mode 100644
index 0000000..89ff053
--- /dev/null
+++ b/sysdeps/mips/tst-abi-fp32mod.c
@@ -0,0 +1,22 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+int
+fp32 (void)
+{
+  return 1;
+}
diff --git a/sysdeps/mips/tst-abi-fp64amod.c b/sysdeps/mips/tst-abi-fp64amod.c
new file mode 100644
index 0000000..be3d781
--- /dev/null
+++ b/sysdeps/mips/tst-abi-fp64amod.c
@@ -0,0 +1,22 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+int
+fp64a (void)
+{
+  return 7;
+}
diff --git a/sysdeps/mips/tst-abi-fp64mod.c b/sysdeps/mips/tst-abi-fp64mod.c
new file mode 100644
index 0000000..04b5c36
--- /dev/null
+++ b/sysdeps/mips/tst-abi-fp64mod.c
@@ -0,0 +1,22 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+int
+fp64 (void)
+{
+  return 6;
+}
diff --git a/sysdeps/mips/tst-abi-fpxxmod.c b/sysdeps/mips/tst-abi-fpxxmod.c
new file mode 100644
index 0000000..69f73a1
--- /dev/null
+++ b/sysdeps/mips/tst-abi-fpxxmod.c
@@ -0,0 +1,22 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+int
+fpxx (void)
+{
+  return 5;
+}
diff --git a/sysdeps/mips/tst-abi-fpxxomod.c b/sysdeps/mips/tst-abi-fpxxomod.c
new file mode 100644
index 0000000..795973b
--- /dev/null
+++ b/sysdeps/mips/tst-abi-fpxxomod.c
@@ -0,0 +1,22 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+int
+fpxxo (void)
+{
+  return 5 + 100;
+}
diff --git a/sysdeps/mips/tst-abi-interlink.c b/sysdeps/mips/tst-abi-interlink.c
new file mode 100644
index 0000000..0777da1
--- /dev/null
+++ b/sysdeps/mips/tst-abi-interlink.c
@@ -0,0 +1,844 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <sys/prctl.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#if defined PR_GET_FP_MODE && defined PR_SET_FP_MODE
+#define HAVE_PRCTL_FP_MODE 1
+#define FR1_MODE (PR_FP_MODE_FR)
+#define FRE_MODE (PR_FP_MODE_FR | PR_FP_MODE_FRE)
+#else
+#define HAVE_PRCTL_FP_MODE 0
+#define FR1_MODE 0x1
+#define FRE_MODE 0x2
+#endif
+
+#define STR_VAL(VAL) #VAL
+#define N_STR(VAL) STR_VAL(VAL)
+
+#define START_STATE(NAME) 					\
+case s_ ## NAME: 						\
+  {								\
+    switch (obj) 						\
+      {
+
+#define END_STATE						\
+      default:							\
+        return false;						\
+      }								\
+  break;							\
+  }
+
+#define NEXT(OBJ, NEXT_STATE)					\
+case o_ ## OBJ: 						\
+  current_fp_state = s_ ## NEXT_STATE;				\
+  break;
+
+#define NEXT_REQ_FR1(OBJ, NEXT_STATE)				\
+case o_ ## OBJ:							\
+  {								\
+    if (has_fr1)						\
+      current_fp_state = s_ ## NEXT_STATE;			\
+    else							\
+      return false;						\
+  }								\
+  break;
+
+#define NEXT_REQ_FR0(OBJ, NEXT_STATE) 				\
+case o_ ## OBJ:							\
+  {								\
+    if (!is_r6							\
+        || (is_r6 && has_fr1 && has_fre))			\
+      current_fp_state = s_ ## NEXT_STATE;			\
+    else 							\
+      return false;						\
+  }								\
+  break;
+
+#define NEXT_REQ_FRE(OBJ, NEXT_STATE)				\
+case o_ ## OBJ: 						\
+  {								\
+    if (has_fr1 && has_fre)					\
+      current_fp_state = s_ ## NEXT_STATE;			\
+    else							\
+      return false;						\
+  }								\
+  break;
+
+#define NEXT_NO_MODE_CHANGE(OBJ, NEXT_STATE)			\
+case o_ ## OBJ: 						\
+  {								\
+    if (current_mode_valid_p (s_ ## NEXT_STATE))			\
+      {								\
+	current_fp_state = s_ ## NEXT_STATE;			\
+	cant_change_mode = true;				\
+      }								\
+    else							\
+      return false;						\
+  }								\
+  break;
+
+static const char * const shared_lib_names[] =
+{
+  "tst-abi-fpanymod.so", "tst-abi-fpsoftmod.so", "tst-abi-fpsinglemod.so",
+  "tst-abi-fp32mod.so", "tst-abi-fp64mod.so", "tst-abi-fp64amod.so",
+  "tst-abi-fpxxmod.so", "tst-abi-fpxxomod.so"
+};
+
+struct fp_mode_req
+{
+  int mode1;
+  int mode2;
+  int mode3;
+};
+
+enum fp_obj
+{
+  o_any,
+  o_soft,
+  o_single,
+  o_fp32,
+  o_fp64,
+  o_fp64a,
+  o_fpxx,
+  o_fpxxo,
+  o_max
+};
+
+enum fp_state
+{
+  s_any,
+  s_soft,
+  s_single,
+  s_fp32,
+  s_fpxx,
+  s_fpxxo,
+  s_fp64a,
+  s_fp64,
+  s_fpxxo_fpxx,
+  s_fp32_fpxx,
+  s_fp32_fpxxo,
+  s_fp32_fpxxo_fpxx,
+  s_fp32_fp64a_fpxx,
+  s_fp32_fp64a_fpxxo,
+  s_fp32_fp64a_fpxxo_fpxx,
+  s_fp64a_fp32,
+  s_fp64a_fpxx,
+  s_fp64a_fpxxo,
+  s_fp64a_fp64,
+  s_fp64a_fp64_fpxx,
+  s_fp64a_fp64_fpxxo,
+  s_fp64a_fpxx_fpxxo,
+  s_fp64a_fp64_fpxxo_fpxx,
+  s_fp64_fpxx,
+  s_fp64_fpxxo,
+  s_fp64_fpxx_fpxxo
+};
+
+
+static int current_fp_mode;
+static bool cant_change_mode = false;
+static bool has_fr1 = false;
+static bool has_fre = false;
+static bool is_r6 = false;
+static unsigned int fp_obj_count[o_max];
+void * shared_lib_ptrs[o_max];
+static enum fp_state current_fp_state = s_any;
+static enum fp_obj test_objects[FPABI_COUNT] = { FPABI_LIST };
+
+/* This function will return the valid FP modes for the specified state.  */
+
+static struct fp_mode_req
+compute_fp_modes (enum fp_state state)
+{
+  struct fp_mode_req requirements;
+
+  requirements.mode1 = -1;
+  requirements.mode2 = -1;
+  requirements.mode3 = -1;
+
+  switch (state)
+    {
+    case s_single:
+      {
+        if (is_r6)
+	  requirements.mode1 = FR1_MODE;
+	else
+	  {
+	    requirements.mode1 = 0;
+	    requirements.mode2 = FR1_MODE;
+	  }
+	break;
+      }
+    case s_fp32:
+    case s_fp32_fpxx:
+    case s_fp32_fpxxo:
+    case s_fp32_fpxxo_fpxx:
+      {
+	if (is_r6)
+	  requirements.mode1 = FRE_MODE;
+	else
+	  {
+	    requirements.mode1 = 0;
+	    requirements.mode2 = FRE_MODE;
+	  }
+	break;
+      }
+    case s_fpxx:
+    case s_fpxxo:
+    case s_fpxxo_fpxx:
+    case s_any:
+    case s_soft:
+      {
+	if (is_r6)
+	  {
+	    requirements.mode1 = FR1_MODE;
+	    requirements.mode2 = FRE_MODE;
+	  }
+	else
+	  {
+	    requirements.mode1 = 0;
+	    requirements.mode2 = FR1_MODE;
+	    requirements.mode3 = FRE_MODE;
+	  }
+	break;
+      }
+    case s_fp64a:
+    case s_fp64a_fpxx:
+    case s_fp64a_fpxxo:
+    case s_fp64a_fpxx_fpxxo:
+      {
+	requirements.mode1 = FR1_MODE;
+	requirements.mode2 = FRE_MODE;
+	break;
+      }
+    case s_fp64:
+    case s_fp64_fpxx:
+    case s_fp64_fpxxo:
+    case s_fp64_fpxx_fpxxo:
+    case s_fp64a_fp64:
+    case s_fp64a_fp64_fpxx:
+    case s_fp64a_fp64_fpxxo:
+    case s_fp64a_fp64_fpxxo_fpxx:
+      {
+	requirements.mode1 = FR1_MODE;
+	break;
+      }
+    case s_fp64a_fp32:
+    case s_fp32_fp64a_fpxx:
+    case s_fp32_fp64a_fpxxo:
+    case s_fp32_fp64a_fpxxo_fpxx:
+      {
+        requirements.mode1 = FRE_MODE;
+        break;
+      }
+    }
+  return requirements;
+}
+
+/* Check the current mode is suitable for the specified state.  */
+
+static bool
+current_mode_valid_p (enum fp_state s)
+{
+  struct fp_mode_req req = compute_fp_modes (s);
+  return (req.mode1 == current_fp_mode
+	  || req.mode2 == current_fp_mode
+	  || req.mode3 == current_fp_mode);
+}
+
+/* Run the state machine by adding a new object.  */
+
+static bool
+set_next_fp_state (enum fp_obj obj)
+{
+  cant_change_mode = false;
+  switch (current_fp_state)
+    {
+
+    START_STATE(soft)
+    NEXT(soft,soft)
+    NEXT(any,soft)
+    END_STATE
+
+    START_STATE(single)
+    NEXT(single,single)
+    NEXT(any,single)
+    END_STATE
+
+    START_STATE(any)
+    NEXT_REQ_FR0(fp32, fp32)
+    NEXT(fpxx, fpxx)
+    NEXT(fpxxo, fpxxo)
+    NEXT_REQ_FR1(fp64a, fp64a)
+    NEXT_REQ_FR1(fp64, fp64)
+    NEXT(any,any)
+    NEXT(soft,soft)
+    NEXT(single,single)
+    END_STATE
+
+    START_STATE(fp32)
+    NEXT_REQ_FR0(fp32,fp32)
+    NEXT(fpxx, fp32_fpxx)
+    NEXT(fpxxo, fp32_fpxxo)
+    NEXT_REQ_FRE(fp64a, fp64a_fp32)
+    NEXT(any,fp32)
+    END_STATE
+
+    START_STATE(fpxx)
+    NEXT_REQ_FR0(fp32, fp32_fpxx)
+    NEXT_REQ_FR1(fp64, fp64_fpxx)
+    NEXT_REQ_FR1(fp64a, fp64a_fpxx)
+    NEXT(fpxxo, fpxxo_fpxx)
+    NEXT(fpxx,fpxx)
+    NEXT(any,fpxx)
+    END_STATE
+
+    START_STATE(fpxxo)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64, fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo,fpxxo)
+    NEXT_NO_MODE_CHANGE(any,fpxxo)
+    END_STATE
+
+    START_STATE(fp64a)
+    NEXT_REQ_FRE(fp32, fp64a_fp32)
+    NEXT_REQ_FR1(fp64, fp64a_fp64)
+    NEXT(fpxxo, fp64a_fpxxo)
+    NEXT(fpxx, fp64a_fpxx)
+    NEXT_REQ_FR1(fp64a, fp64a)
+    NEXT(any, fp64a)
+    END_STATE
+
+    START_STATE(fp64)
+    NEXT_REQ_FR1(fp64a, fp64a_fp64)
+    NEXT(fpxxo, fp64_fpxxo)
+    NEXT(fpxx, fp64_fpxx)
+    NEXT_REQ_FR1(fp64, fp64)
+    NEXT(any, fp64)
+    END_STATE
+
+    START_STATE(fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp64, fp64_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo, fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(any, fpxxo_fpxx)
+    END_STATE
+
+    START_STATE(fp32_fpxx)
+    NEXT_REQ_FR0(fp32, fp32_fpxx)
+    NEXT(fpxx, fp32_fpxx)
+    NEXT(fpxxo, fp32_fpxxo_fpxx)
+    NEXT_REQ_FRE(fp64a, fp32_fp64a_fpxx)
+    NEXT(any, fp32_fpxx)
+    END_STATE
+
+    START_STATE(fp32_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp32_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fp32_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp64a, fp32_fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(any, fp32_fpxxo)
+    END_STATE
+
+    START_STATE(fp32_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp32_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxx, fp32_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp64a, fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(any, fp32_fpxxo_fpxx)
+    END_STATE
+
+    START_STATE(fp64a_fp32)
+    NEXT_REQ_FRE(fp32, fp64a_fp32)
+    NEXT_REQ_FRE(fp64a, fp64a_fp32)
+    NEXT(fpxxo, fp32_fp64a_fpxxo)
+    NEXT(fpxx, fp32_fp64a_fpxx)
+    NEXT(any, fp64a_fp32)
+    END_STATE
+
+    START_STATE(fp64a_fpxx)
+    NEXT_REQ_FRE(fp32, fp32_fp64a_fpxx)
+    NEXT_REQ_FR1(fp64a, fp64a_fpxx)
+    NEXT(fpxx, fp64a_fpxx)
+    NEXT(fpxxo, fp64a_fpxx_fpxxo)
+    NEXT_REQ_FR1(fp64, fp64a_fp64_fpxx)
+    NEXT(any, fp64a_fpxx)
+    END_STATE
+
+    START_STATE(fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fp64a_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64, fp64a_fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(any, fp64a_fpxxo)
+    END_STATE
+
+    START_STATE(fp64a_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fp64a_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp64a_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64, fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(any, fp64a_fpxx_fpxxo)
+    END_STATE
+
+    START_STATE(fp64_fpxx)
+    NEXT_REQ_FR1(fp64a, fp64a_fp64_fpxx)
+    NEXT(fpxxo, fp64_fpxx_fpxxo)
+    NEXT(fpxx, fp64_fpxx)
+    NEXT_REQ_FR1(fp64, fp64_fpxx)
+    NEXT(any, fp64_fpxx)
+    END_STATE
+
+    START_STATE(fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fp64_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64, fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(any, fp64_fpxxo)
+    END_STATE
+
+    START_STATE(fp64_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp64_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fp64_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64, fp64_fpxx_fpxxo)
+    NEXT_NO_MODE_CHANGE(any, fp64_fpxx_fpxxo)
+    END_STATE
+
+    START_STATE(fp64a_fp64)
+    NEXT_REQ_FR1(fp64a, fp64a_fp64)
+    NEXT(fpxxo, fp64a_fp64_fpxxo)
+    NEXT(fpxx, fp64a_fp64_fpxx)
+    NEXT_REQ_FR1(fp64, fp64a_fp64)
+    NEXT(any, fp64a_fp64)
+    END_STATE
+
+    START_STATE(fp64a_fp64_fpxx)
+    NEXT_REQ_FR1(fp64a, fp64a_fp64_fpxx)
+    NEXT(fpxxo, fp64a_fp64_fpxxo_fpxx)
+    NEXT(fpxx, fp64a_fp64_fpxx)
+    NEXT_REQ_FR1(fp64, fp64a_fp64_fpxx)
+    NEXT(any, fp64a_fp64_fpxx)
+    END_STATE
+
+    START_STATE(fp64a_fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp64a_fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64, fp64a_fp64_fpxxo)
+    NEXT_NO_MODE_CHANGE(any, fp64a_fp64_fpxxo)
+    END_STATE
+
+    START_STATE(fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp64a, fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxx, fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp64, fp64a_fp64_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(any, fp64a_fp64_fpxxo_fpxx)
+    END_STATE
+
+    START_STATE(fp32_fp64a_fpxx)
+    NEXT_REQ_FRE(fp32, fp32_fp64a_fpxx)
+    NEXT_REQ_FRE(fp64a, fp32_fp64a_fpxx)
+    NEXT(fpxxo, fp32_fp64a_fpxxo_fpxx)
+    NEXT(fpxx, fp32_fp64a_fpxx)
+    NEXT(any, fp32_fp64a_fpxx)
+    END_STATE
+
+    START_STATE(fp32_fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fp64a, fp32_fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(fpxx, fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp32_fp64a_fpxxo)
+    NEXT_NO_MODE_CHANGE(any, fp32_fp64a_fpxxo)
+    END_STATE
+
+    START_STATE(fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp32, fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fp64a, fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxx, fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(fpxxo, fp32_fp64a_fpxxo_fpxx)
+    NEXT_NO_MODE_CHANGE(any, fp32_fp64a_fpxxo_fpxx)
+    END_STATE
+    }
+
+  if (obj != o_max)
+    fp_obj_count[obj]++;
+
+  return true;
+}
+
+/* Run the state machine by removing an object.  */
+
+static bool
+remove_object (enum fp_obj obj)
+{
+  if (obj == o_max)
+    return false;
+
+  fp_obj_count[obj]--;
+
+  /* We can't change fp state until all the objects
+     of a particular type have been unloaded.  */
+  if (fp_obj_count[obj] != 0)
+    return false;
+
+  switch (current_fp_state)
+    {
+    START_STATE(soft)
+    NEXT(soft,any)
+    END_STATE
+
+    START_STATE(single)
+    NEXT(single,any)
+    END_STATE
+
+    START_STATE(any)
+    NEXT(any,any)
+    END_STATE
+
+    START_STATE(fp32)
+    NEXT (fp32,any)
+    END_STATE
+
+    START_STATE(fpxx)
+    NEXT (fpxx,any)
+    END_STATE
+
+    START_STATE(fpxxo)
+    NEXT (fpxxo,any)
+    END_STATE
+
+    START_STATE(fp64a)
+    NEXT(fp64a, any)
+    END_STATE
+
+    START_STATE(fp64)
+    NEXT(fp64, any)
+    END_STATE
+
+    START_STATE(fpxxo_fpxx)
+    NEXT(fpxx, fpxxo)
+    NEXT(fpxxo, fpxx)
+    END_STATE
+
+    START_STATE(fp32_fpxx)
+    NEXT(fp32, fpxx)
+    NEXT(fpxx, fp32)
+    END_STATE
+
+    START_STATE(fp32_fpxxo)
+    NEXT(fp32, fpxxo)
+    NEXT(fpxxo, fp32)
+    END_STATE
+
+    START_STATE(fp32_fpxxo_fpxx)
+    NEXT(fp32, fpxxo_fpxx)
+    NEXT(fpxxo, fp32_fpxx)
+    NEXT(fpxx, fp32_fpxxo)
+    END_STATE
+
+    START_STATE(fp64a_fp32)
+    NEXT(fp32, fp64a)
+    NEXT(fp64a, fp32)
+    END_STATE
+
+    START_STATE(fp64a_fpxx)
+    NEXT(fp64a, fpxx)
+    NEXT(fpxx, fp64a)
+    END_STATE
+
+    START_STATE(fp64a_fpxxo)
+    NEXT(fp64a, fpxxo)
+    NEXT(fpxxo, fp64a)
+    END_STATE
+
+    START_STATE(fp64a_fpxx_fpxxo)
+    NEXT(fp64a, fpxxo_fpxx)
+    NEXT(fpxx, fp64a_fpxxo)
+    NEXT(fpxxo, fp64a_fpxx)
+    END_STATE
+
+    START_STATE(fp64_fpxx)
+    NEXT(fpxx, fp64)
+    NEXT(fp64, fpxx)
+    END_STATE
+
+    START_STATE(fp64_fpxxo)
+    NEXT(fpxxo, fp64)
+    NEXT(fp64, fpxxo)
+    END_STATE
+
+    START_STATE(fp64_fpxx_fpxxo)
+    NEXT(fp64, fpxxo_fpxx)
+    NEXT(fpxxo, fp64_fpxx)
+    NEXT(fpxx, fp64_fpxxo)
+    END_STATE
+
+    START_STATE(fp64a_fp64)
+    NEXT(fp64a, fp64)
+    NEXT(fp64, fp64a)
+    END_STATE
+
+    START_STATE(fp64a_fp64_fpxx)
+    NEXT(fp64a, fp64_fpxx)
+    NEXT(fpxx, fp64a_fp64)
+    NEXT(fp64, fp64a_fpxx)
+    END_STATE
+
+    START_STATE(fp64a_fp64_fpxxo)
+    NEXT(fp64a, fp64_fpxxo)
+    NEXT(fpxxo, fp64a_fp64)
+    NEXT(fp64, fp64a_fpxxo)
+    END_STATE
+
+    START_STATE(fp64a_fp64_fpxxo_fpxx)
+    NEXT(fp64a, fp64_fpxx_fpxxo)
+    NEXT(fpxx, fp64a_fp64_fpxxo)
+    NEXT(fpxxo, fp64a_fp64_fpxx)
+    NEXT(fp64, fp64a_fpxx_fpxxo)
+    END_STATE
+
+    START_STATE(fp32_fp64a_fpxx)
+    NEXT(fp32, fp64a_fpxx)
+    NEXT(fp64a, fp32_fpxx)
+    NEXT(fpxx, fp64a_fp32)
+    END_STATE
+
+    START_STATE(fp32_fp64a_fpxxo)
+    NEXT(fp32, fp64a_fpxxo)
+    NEXT(fp64a, fp32_fpxxo)
+    NEXT(fpxxo, fp64a_fp32)
+    END_STATE
+
+    START_STATE(fp32_fp64a_fpxxo_fpxx)
+    NEXT(fp32, fp64a_fpxx_fpxxo)
+    NEXT(fp64a, fp32_fpxxo_fpxx)
+    NEXT(fpxx, fp32_fp64a_fpxxo)
+    NEXT(fpxxo, fp32_fp64a_fpxx)
+    END_STATE
+    }
+
+  return true;
+}
+
+static int
+mode_transition_valid_p (void)
+{
+  int prev_fp_mode;
+
+  /* Get the current fp mode.  */
+  prev_fp_mode = current_fp_mode;
+#if HAVE_PRCTL_FP_MODE
+  current_fp_mode = prctl (PR_GET_FP_MODE);
+
+  /* If the prctl call fails assume the core only has FR0 mode support.  */
+  if (current_fp_mode == -1)
+    current_fp_mode = 0;
+#endif
+
+  if (!current_mode_valid_p (current_fp_state))
+    return 0;
+
+  /* Check if mode changes are not allowed but a mode change happened.  */
+  if (cant_change_mode
+      && current_fp_mode != prev_fp_mode)
+    return 0;
+
+  return 1;
+}
+
+/* Load OBJ and check that it was/was not loaded correctly.  */
+bool
+load_object (enum fp_obj obj)
+{
+  bool should_load = set_next_fp_state (obj);
+
+  shared_lib_ptrs[obj] = dlopen (shared_lib_names[obj], RTLD_LAZY);
+
+  /* If we expected an error and the load was successful then fail.  */
+  if (!should_load && (shared_lib_ptrs[obj] != 0))
+    return false;
+
+  if (should_load && (shared_lib_ptrs[obj] == 0))
+    return false;
+
+  if (!mode_transition_valid_p ())
+    return false;
+
+  return true;
+}
+
+/* Remove an object and check the state remains valid.  */
+bool
+unload_object (enum fp_obj obj)
+{
+  if (!shared_lib_ptrs[obj])
+    return true;
+
+  remove_object (obj);
+
+  if (dlclose (shared_lib_ptrs[obj]) != 0)
+    return false;
+
+  shared_lib_ptrs[obj] = 0;
+
+  if (!mode_transition_valid_p ())
+    return false;
+
+  return true;
+}
+
+/* Load every permuation of OBJECTS.  */
+static bool
+test_permutations (enum fp_obj objects[], int count)
+{
+  int i;
+
+  for (i = 0 ; i < count ; i++)
+    {
+      if (!load_object (objects[i]))
+	return false;
+
+      if (count > 1)
+	{
+	  enum fp_obj new_objects[count - 1];
+	  int j;
+	  int k = 0;
+
+	  for (j = 0 ; j < count ; j++)
+	    {
+	      if (j != i)
+		new_objects[k++] = objects[j];
+	    }
+
+	  if (!test_permutations (new_objects, count - 1))
+	    return false;
+	}
+
+      if (!unload_object (objects[i]))
+	return false;
+    }
+  return true;
+}
+
+int
+do_test (void)
+{
+#if HAVE_PRCTL_FP_MODE
+  /* Determine available hardware support and current mode.  */
+  current_fp_mode = prctl (PR_GET_FP_MODE);
+
+  /* If the prctl call fails assume the core only has FR0 mode support.  */
+  if (current_fp_mode == -1)
+    current_fp_mode = 0;
+  else
+    {
+      if (prctl (PR_SET_FP_MODE, 0) != 0)
+	{
+	  if (errno == ENOTSUP)
+	    is_r6 = true;
+	  else
+	    {
+	      printf ("unexpected error from PR_SET_FP_MODE, 0: %m\n");
+	      return 1;
+	    }
+	}
+
+      if (prctl (PR_SET_FP_MODE, PR_FP_MODE_FR) != 0)
+	{
+	  if (errno != ENOTSUP)
+	    {
+	      printf ("unexpected error from PR_SET_FP_MODE, "
+		      "PR_FP_MODE_FR: %m\n");
+	      return 1;
+	    }
+	}
+      else
+	has_fr1 = true;
+
+      if (prctl (PR_SET_FP_MODE, PR_FP_MODE_FR | PR_FP_MODE_FRE) != 0)
+	{
+	  if (errno != ENOTSUP)
+	    {
+	      printf ("unexpected error from PR_SET_FP_MODE, "
+		      "PR_FP_MODE_FR | PR_FP_MODE_FRE: %m\n");
+	      return 1;
+	    }
+	}
+      else
+	has_fre = true;
+
+      if (prctl (PR_SET_FP_MODE, current_fp_mode) != 0)
+	{
+	  printf ("unable to restore initial FP mode: %m\n");
+	  return 1;
+	}
+    }
+
+  if ((is_r6 && !(current_fp_mode & PR_FP_MODE_FR))
+      || (!has_fr1 && (current_fp_mode & PR_FP_MODE_FR))
+      || (!has_fre && (current_fp_mode & PR_FP_MODE_FRE)))
+    {
+      puts ("Inconsistency detected between initial FP mode "
+	    "and supported FP modes\n");
+      return 1;
+    }
+#else
+  current_fp_mode = 0;
+#endif
+
+  /* Set up the initial state from executable and LDSO.  Assumptions:
+     1) All system libraries have the same ABI as ld.so.
+     2) Due to the fact that ld.so is tested by invoking it directly
+        rather than via an interpreter, there is no point in varying
+	the ABI of the test program.  Instead the ABI only varies for
+	the shared libraries which get loaded.  */
+  if (!set_next_fp_state (FPABI_NATIVE))
+    {
+      puts ("Unable to enter initial ABI state\n");
+      return 1;
+    }
+
+  /* Compare the computed state with the hardware state.  */
+  if (!mode_transition_valid_p ())
+    return 1;
+
+  /* Run all possible test permutations.  */
+  if (!test_permutations (test_objects, FPABI_COUNT))
+    {
+      puts ("Mode checks failed\n");
+      return 1;
+    }
+
+  return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../../test-skeleton.c"
diff --git a/sysdeps/mips/tst-mode-switch-1.c b/sysdeps/mips/tst-mode-switch-1.c
new file mode 100644
index 0000000..59284b2
--- /dev/null
+++ b/sysdeps/mips/tst-mode-switch-1.c
@@ -0,0 +1,120 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/prctl.h>
+
+#if __mips_fpr != 0 || _MIPS_SPFPSET != 16
+#error This test requires -mfpxx -mno-odd-spreg
+#endif
+
+/* This test verifies that mode changes do not clobber register state
+   in other threads.  */
+
+static volatile int finished;
+static int mode[6] = { 0,
+		       PR_FP_MODE_FR,
+		       PR_FP_MODE_FR | PR_FP_MODE_FRE,
+		       PR_FP_MODE_FR,
+		       0,
+		       PR_FP_MODE_FR | PR_FP_MODE_FRE };
+
+static void *
+thread_function (void * arg __attribute__ ((unused)))
+{
+  volatile int i = 0;
+  volatile float f = 0.0;
+  volatile double d = 0.0;
+
+  while (!finished)
+    {
+      if ((float)i != f || (double)i != d)
+	{
+	  printf ("unexpected value: i(%d) f(%f) d(%f)\n", i, f, d);
+	  exit (1);
+	}
+
+      if (i == 100)
+	{
+	  i = 0;
+	  f = 0.0;
+	  d = 0.0;
+	}
+
+      i++;
+      f++;
+      d++;
+    }
+  return NULL;
+}
+
+int
+main (void)
+{
+  int count = sysconf (_SC_NPROCESSORS_ONLN);
+  if (count <= 0)
+    count = 1;
+  count *= 4;
+
+  pthread_t th[count];
+  int i;
+  int result = 0;
+
+  for (i = 0; i < count; ++i)
+    if (pthread_create (&th[i], NULL, thread_function, 0) != 0)
+      {
+	printf ("creation of thread %d failed\n", i);
+	exit (1);
+      }
+
+  for (i = 0 ; i < 1000000 ; i++)
+    {
+      if (prctl (PR_SET_FP_MODE, mode[i % 6]) != 0
+	  && errno != ENOTSUP)
+	{
+	  printf ("prctl PR_SET_FP_MODE failed: %m\n");
+	  exit (1);
+	}
+    }
+
+  finished = 1;
+
+  for (i = 0; i < count; ++i)
+    {
+      void *v;
+      if (pthread_join (th[i], &v) != 0)
+	{
+	  printf ("join of thread %d failed\n", i);
+	  result = 1;
+	}
+      else if (v != NULL)
+	{
+	  printf ("join %d successful, but child failed\n", i);
+	  result = 1;
+	}
+      else
+	printf ("join %d successful\n", i);
+    }
+
+  return result;
+}
diff --git a/sysdeps/mips/tst-mode-switch-2.c b/sysdeps/mips/tst-mode-switch-2.c
new file mode 100644
index 0000000..18b016e
--- /dev/null
+++ b/sysdeps/mips/tst-mode-switch-2.c
@@ -0,0 +1,160 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <pthread.h>
+#include <sys/prctl.h>
+
+#if __mips_fpr != 0 || _MIPS_SPFPSET != 16
+#error This test requires -mfpxx -mno-odd-spreg
+#endif
+
+/* This test verifies that all threads in a process see a mode
+   change when any thread causes a mode change.  */
+
+static int mode[6] = { 0,
+		       PR_FP_MODE_FR,
+		       PR_FP_MODE_FR | PR_FP_MODE_FRE,
+		       PR_FP_MODE_FR,
+		       0,
+		       PR_FP_MODE_FR | PR_FP_MODE_FRE };
+static volatile int current_mode;
+static volatile int finished;
+static pthread_barrier_t barr_ready;
+static pthread_barrier_t barr_cont;
+
+static void *
+thread_function (void * arg __attribute__ ((unused)))
+{
+  while (!finished)
+    {
+      int res = pthread_barrier_wait (&barr_ready);
+
+      if (res != 0 && res != PTHREAD_BARRIER_SERIAL_THREAD)
+	{
+	  printf ("barrier wait failed: %m\n");
+	  exit (1);
+	}
+
+      int mode = prctl (PR_GET_FP_MODE);
+
+      if (mode != current_mode)
+	{
+	  printf ("unexpected mode: %d != %d\n", mode, current_mode);
+	  exit (1);
+	}
+
+      res = pthread_barrier_wait (&barr_cont);
+
+      if (res != 0 && res != PTHREAD_BARRIER_SERIAL_THREAD)
+	{
+	  printf ("barrier wait failed: %m\n");
+	  exit (1);
+	}
+    }
+  return NULL;
+}
+
+int
+main (void)
+{
+  int count = sysconf (_SC_NPROCESSORS_ONLN);
+  if (count <= 0)
+    count = 1;
+  count *= 4;
+
+  pthread_t th[count];
+  int i;
+  int result = 0;
+
+  if (pthread_barrier_init (&barr_ready, NULL, count + 1) != 0)
+    {
+      printf ("failed to initialize barrier: %m\n");
+      exit (1);
+    }
+
+  if (pthread_barrier_init (&barr_cont, NULL, count + 1) != 0)
+    {
+      printf ("failed to initialize barrier: %m\n");
+      exit (1);
+    }
+
+  for (i = 0; i < count; ++i)
+    if (pthread_create (&th[i], NULL, thread_function, 0) != 0)
+      {
+	printf ("creation of thread %d failed\n", i);
+	exit (1);
+      }
+
+  for (i = 0 ; i < 7 ; i++)
+    {
+      if (prctl (PR_SET_FP_MODE, mode[i % 6]) != 0)
+	{
+	  if (errno != ENOTSUP)
+	    {
+	      printf ("prctl PR_SET_FP_MODE failed: %m");
+	      exit (1);
+	    }
+	}
+      else
+	current_mode = mode[i % 6];
+
+      
+      int res = pthread_barrier_wait (&barr_ready);
+
+      if (res != 0 && res != PTHREAD_BARRIER_SERIAL_THREAD)
+	{
+	  printf ("barrier wait failed: %m\n");
+	  exit (1);
+	}
+
+      if (i == 6)
+	finished = 1;
+
+      res = pthread_barrier_wait (&barr_cont);
+
+      if (res != 0 && res != PTHREAD_BARRIER_SERIAL_THREAD)
+	{
+	  printf ("barrier wait failed: %m\n");
+	  exit (1);
+	}
+    }
+
+  for (i = 0; i < count; ++i)
+    {
+      void *v;
+      if (pthread_join (th[i], &v) != 0)
+	{
+	  printf ("join of thread %d failed\n", i);
+	  result = 1;
+	}
+      else if (v != NULL)
+	{
+	  printf ("join %d successful, but child failed\n", i);
+	  result = 1;
+	}
+      else
+	printf ("join %d successful\n", i);
+    }
+
+  return result;
+}
diff --git a/sysdeps/mips/tst-mode-switch-3.c b/sysdeps/mips/tst-mode-switch-3.c
new file mode 100644
index 0000000..aaedb61
--- /dev/null
+++ b/sysdeps/mips/tst-mode-switch-3.c
@@ -0,0 +1,87 @@ 
+/* Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <sys/prctl.h>
+
+#if __mips_fpr != 0 || _MIPS_SPFPSET != 16
+#error This test requires -mfpxx -mno-odd-spreg
+#endif
+
+/* This test verifies that mode changes between a setjmp and longjmp do
+   not corrupt the state of callee-saved registers.  */
+
+static int mode[6] = { 0,
+		       PR_FP_MODE_FR,
+		       PR_FP_MODE_FR | PR_FP_MODE_FRE,
+		       PR_FP_MODE_FR,
+		       0,
+		       PR_FP_MODE_FR | PR_FP_MODE_FRE };
+static jmp_buf env;
+float check1 = 2.0;
+double check2 = 3.0;
+
+int
+main (void)
+{
+  int i;
+  int result = 0;
+
+  for (i = 0 ; i < 7 ; i++)
+    {
+      int retval;
+      register float test1 __asm ("$f20");
+      register double test2 __asm ("$f22");
+
+      /* Hide what we are doing to $f20 and $f22 from the compiler.  */
+      __asm __volatile ("l.s %0,%2\n"
+			"l.d %1,%3\n"
+			: "=f" (test1), "=f" (test2)
+			: "m" (check1), "m" (check2));
+
+      retval = setjmp (env);
+
+      /* Make sure the compiler knows we want to access the variables
+         via the named registers again.  */
+      __asm __volatile ("" : : "f" (test1), "f" (test2));
+
+      if (test1 != check1 || test2 != check2)
+	{
+	  printf ("Corrupt register detected: $20 %f = %f, $22 %f = %f\n",
+		  test1, check1, test2, check2);
+	  result = 1;
+	}
+
+      if (retval == 0)
+	{
+	  if (prctl (PR_SET_FP_MODE, mode[i % 6]) != 0
+	      && errno != ENOTSUP)
+	    {
+	      printf ("prctl PR_SET_FP_MODE failed: %m");
+	      exit (1);
+	    }
+	  longjmp (env, 0);
+	}
+    }
+
+  return result;
+}
diff --git a/sysdeps/unix/sysv/linux/mips/configure b/sysdeps/unix/sysv/linux/mips/configure
index 6db1f80..1a51319 100644
--- a/sysdeps/unix/sysv/linux/mips/configure
+++ b/sysdeps/unix/sysv/linux/mips/configure
@@ -105,6 +105,148 @@  if test -z "$libc_mips_float"; then
   as_fn_error $? "could not determine if compiler is using hard or soft floating point ABI" "$LINENO" 5
 fi
 
+libc_mips_o32_fp=
+
+if test x"$libc_mips_abi" = xo32 -a x"$libc_mips_float" = xhard; then
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+      #if !defined(__mips_fpr)
+      #error Missing FPR sizes
+      #endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+        #if (__mips_fpr != 32)
+        #error Not FP32
+        #endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  libc_mips_o32_fp=32
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+          #if (__mips_fpr != 0) || !defined(_MIPS_SPFPSET) || (_MIPS_SPFPSET != 16)
+          #error Not FPXX (without odd single-precision registers)
+          #endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  libc_mips_o32_fp=xx
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+            #if (__mips_fpr != 0)
+            #error Not FPXX (with odd single precision registers)
+            #endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  libc_mips_o32_fp=xxo
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+              #if (__mips_fpr != 64) || !defined(_MIPS_SPFPSET) || (_MIPS_SPFPSET != 16)
+              #error Not FP64A
+              #endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  libc_mips_o32_fp=64a
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+                #if (__mips_fpr != 64)
+                #error Not FP64
+                #endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  libc_mips_o32_fp=64
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+config_vars="$config_vars
+o32-fpabi = ${libc_mips_o32_fp}"
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+    #include <sys/prctl.h>
+    #if !defined(PR_GET_FP_MODE) || !defined(PR_SET_FP_MODE)
+    #error New prctl support for setting FP modes not found
+    #endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  libc_mips_mode_switch=yes
+else
+  libc_mips_mode_switch=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+config_vars="$config_vars
+mips-mode-switch = ${libc_mips_mode_switch}"
+
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
 $as_echo_n "checking for grep that handles long lines and -e... " >&6; }
diff --git a/sysdeps/unix/sysv/linux/mips/configure.ac b/sysdeps/unix/sysv/linux/mips/configure.ac
index c3f217a..7329a55 100644
--- a/sysdeps/unix/sysv/linux/mips/configure.ac
+++ b/sysdeps/unix/sysv/linux/mips/configure.ac
@@ -44,6 +44,59 @@  if test -z "$libc_mips_float"; then
   AC_MSG_ERROR([could not determine if compiler is using hard or soft floating point ABI])
 fi
 
+libc_mips_o32_fp=
+
+if test x"$libc_mips_abi" = xo32 -a x"$libc_mips_float" = xhard; then
+  AC_COMPILE_IFELSE(
+    [AC_LANG_PROGRAM([
+      #if !defined(__mips_fpr)
+      #error Missing FPR sizes
+      #endif])],
+    [AC_COMPILE_IFELSE(
+      [AC_LANG_PROGRAM([
+        #if (__mips_fpr != 32)
+        #error Not FP32
+        #endif])],
+      [libc_mips_o32_fp=32],
+      [AC_COMPILE_IFELSE(
+        [AC_LANG_PROGRAM([
+          #if (__mips_fpr != 0) || !defined(_MIPS_SPFPSET) || (_MIPS_SPFPSET != 16)
+          #error Not FPXX (without odd single-precision registers)
+          #endif])],
+        [libc_mips_o32_fp=xx],
+        [AC_COMPILE_IFELSE(
+          [AC_LANG_PROGRAM([
+            #if (__mips_fpr != 0)
+            #error Not FPXX (with odd single precision registers)
+            #endif])],
+          [libc_mips_o32_fp=xxo],
+          [AC_COMPILE_IFELSE(
+            [AC_LANG_PROGRAM([
+              #if (__mips_fpr != 64) || !defined(_MIPS_SPFPSET) || (_MIPS_SPFPSET != 16)
+              #error Not FP64A
+              #endif])],
+            [libc_mips_o32_fp=64a],
+            [AC_COMPILE_IFELSE(
+              [AC_LANG_PROGRAM([
+                #if (__mips_fpr != 64)
+                #error Not FP64
+                #endif])],
+              [libc_mips_o32_fp=64],
+              [])])])])])],
+    [])
+fi
+LIBC_CONFIG_VAR([o32-fpabi],[${libc_mips_o32_fp}])
+
+AC_COMPILE_IFELSE(
+  [AC_LANG_PROGRAM([
+    #include <sys/prctl.h>
+    #if !defined(PR_GET_FP_MODE) || !defined(PR_SET_FP_MODE)
+    #error New prctl support for setting FP modes not found
+    #endif])],
+  [libc_mips_mode_switch=yes],
+  [libc_mips_mode_switch=no])
+LIBC_CONFIG_VAR([mips-mode-switch],[${libc_mips_mode_switch}])
+
 AC_CACHE_CHECK([whether the compiler is using the 2008 NaN encoding],
   libc_cv_mips_nan2008, [AC_EGREP_CPP(yes, [dnl
 #ifdef __mips_nan2008
diff --git a/sysdeps/unix/sysv/linux/mips/ldsodefs.h b/sysdeps/unix/sysv/linux/mips/ldsodefs.h
index d7c62f4..70f8f16 100644
--- a/sysdeps/unix/sysv/linux/mips/ldsodefs.h
+++ b/sysdeps/unix/sysv/linux/mips/ldsodefs.h
@@ -34,7 +34,7 @@  extern void _dl_static_init (struct link_map *map);
 #undef VALID_ELF_ABIVERSION
 #define VALID_ELF_ABIVERSION(osabi,ver)			\
   (ver == 0						\
-   || (osabi == ELFOSABI_SYSV && ver < 2)		\
+   || (osabi == ELFOSABI_SYSV && ver < 4)		\
    || (osabi == ELFOSABI_GNU && ver < LIBC_ABI_MAX))
 
 #endif /* ldsodefs.h */