public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
@ 2014-10-02 20:07 Matthew Fortune
  2014-10-02 20:55 ` Joseph S. Myers
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Fortune @ 2014-10-02 20:07 UTC (permalink / raw)
  To: 'libc-alpha@sourceware.org'
  Cc: 'Joseph Myers',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

[-- Attachment #1: Type: text/plain, Size: 73342 bytes --]

Apologies for the exceptionally long time between last posting this work. I have
addressed all previous comments. The spec has been heavily updated on:

https://dmz-portal.mips.com/wiki/MIPS_O32_ABI_-_FR0_and_FR1_Interlinking

The comments below should probably end up on the glibc wiki and explain the
various (in)compatibilities involved in this work. The binutils part has been
committed to trunk but GCC work is unfortunately still pending:

https://gcc.gnu.org/ml/gcc-patches/2014-08/msg02172.html

I'm afraid I see no way to break this work down and it is essential in order
to support MIPS32r2+64-bit FPRs, MIPS32r5+MSA and MIPS32r6. Any and all
review comments are welcome of course.

= Overall changes =

== pre-PT_MIPS_ABIFLAGS ==

Soft-float and hard-float executables use the same dynamic linkers although
NAN2008 variants use a distinct dynamic linker.
ELF class + flags enable ld.so to distinguish between the three major ABIs and
detect NAN2008.
ABIVERSION 1 indicates use of MIPS PLT
ABIVERSION 2 indicates use of STB_UNIQUE symbols
The maximum supported ABIVERSION in glibc is 2.

== post-PT_MIPS_ABIFLAGS ==

There is no change to the range of dynamic linkers.
There is no change to the way in which overall ABI and NAN encoding are
represented.
A new program header 'PT_MIPS_ABIFLAGS' indicates the presence of a structure
containing a variety of ABI and architecture related information which includes
the FPABI value.
Two new O32 FPABI values (6 and 7) are not link compatible with
pre-PT_MIPS_ABIFLAGS and the use of these values triggers an increase in the
ABIVERSION. ABIVERSION 3 indicates the use of an FP64 or FP64A ABI extension.

== ELF ABI markers ==

For O32 the following binaries can therefore exist:

              ABIVERSION  PT_MIPS_ABIFLAGS    FPABI     Notes
soft-float       <= 2            no          unknown
double-float     <= 2            no          unknown
single-float     <= 2            no          unknown    Not supported in glibc
soft-float       <= 2            yes            3
double-float     <= 2            yes            1
single-float     <= 2            yes            2
fpxx             <= 2            yes            5
fp64             >= 3            yes            6
fp64a            >= 3            yes            7

ld.so is guaranteed to be PT_MIPS_ABIFLAGS aware from ABIVERSION 3 onwards.

= Dynamic link logic =

The logic which implements PT_MIPS_ABIFLAGS handling is not dependent on whether
The compiler or binutils used to build ld.so has PT_MIPS_ABIFLAGS support. The
logic is however dependent on whether the kernel headers support the new prctl
options. When compiled against a kernel without PR_GET_FP_MODE support the logic
reduces to expect that all O32 processes will execute with FR=0 (or equivalent).

The code is also written to cope with pre-existing kernels which do not have
the new prctl options defined as part of this work.

== Using new executables with old dynamic linkers ==

Since a pre-existing dynamic linker does not know about PT_MIPS_ABIFLAGS the
only checks which will occur are for overall ABI and NAN encoding. The existing
logic has no mechanism to distinguish between soft and hard float and using new
soft or hard float executables will be as (un)safe as old executables. The new
fpxx ABI extension is both link compatible with hard-float and safe to execute
with FR=0 and will be accepted by existing logic with the same caveat as not
being able to distinguish between soft and hard float.

The fp64 and fp64a extensions will be rejected by any pre-existing dynamic
linker so any shared library using these extensions will not be loaded.
However, since there are no ABIVERSION checks in the Linux kernel program
loader then executables will be loaded and crash as they will execute with the
wrong FR mode.

== Using old executables with new dynamic linkers ==

pre-PT_MIPS_ABIFLAGS soft and hard float executables are indistinguishable and
when seen by the PT_MIPS_ABIFLAGS dynamic linker logic will be considered as
hard-float. This means that a pre-existing soft, hard, or mixed world will still
link with the same safety that was afforded by pre-PT_MIPS_ABIFLAGS ld.so.

== Mixing old and new executables with pre-PT_MIPS_ABIFLAGS dynamic linkers ==

This is covered by the statements above as pre-PT_MIPS_ABIFLAGS dynamic linkers
could not detect any differences between ABI extensions anyway so there are no
new compatibility problems.

== Mixing old and new executables with post-PT_MIPS_ABIFLAGS dynamic linkers ==

One caveat appears when mixing old soft-float with new soft-float. Since the old
soft-float binaries had no dynamic linker visible markings then
post-PT_MIPS_ABIFLAGS logic detects these as hard-float. This means that mixing
old and new soft-float is not possible and will be rejected. Since soft-float is
not a publicly supported ABI variant for any major MIPS Linux distribution it
seems reasonable for this to take the hit of needing a re-build.

= Test Methodology =

Testing for ld.so is focussed entirely on the range of inputs it can receive
and not on the ways in which those can be produced. The distinguishing factors
are listed in the table above. A utility has been written to manipulate the
PT_MIPS_ABIFLAGS header and section content to allow all the possible objects
to be created without having to build toolchains for each ABI variant.

The tool can do any of the following:
1) Change a PT_MIPS_ABIFLAGS header to PT_NULL. This effectively creates a
   pre-PT_MIPS_ABIFLAGS binary from the perspective of ld.so
2) Corrupt the size field of a PT_MIPS_ABIFLAGS header to make the binary
   illegal.
3) Set the FPABI and odd-spreg flags in the .MIPS.abiflags section to any value

With this utility the testsuite creates a dynamic linker, executable and shared
library with every ABI extension. The original toolchain which these are built
with should ideally be FPXX so that the generated code is compatible with all
other ABI variants which are forcefully set. A copy of libc.so and libdl.so are
also made with their ABIs set to 'any' so that they effectively take no part in
the ABI testing.

All combinations of the executable and dynamic linker are produced to exercise
the kernel loader. Tests are categorised to indicate which should load and
which should not. Some combinations are simply forbidden and some depend on
hardware support.

For the tests which successfully load then the test code takes over to verify
all ld.so loader behaviour.

The test code includes a state machine which tracks which extensions are loaded
at any given point, which extensions can be loaded, the current hardware mode
and the available hardware modes. Combinations of shared libraries are then
passed to the test which loads them and compares the loader behaviour with
the state machine.

The testsuite is attached as abiflags-v1.tgz. The tests can be run natively
or via qemu user-mode emulation. I have experimental qemu patches which
add PT_MIPS_ABIFLAGS support to user-mode emulation and my colleagues have
kernel patches but I am not sure if they have been published yet.

== GLIBC tests ==

A cut down version of the full FPXX testsuite is included in the GLIBC test
system and covers as many ABI variants as the build configuration allows. The
glibc version of the test performs load and unload to exhaustively cover the
double-float ABI variants.

Tests to cover the basic operation of multi-threaded mode-switching are also
included and should provide reasonable assurance that all the underlying
kernel features are working.

== Manual tests ==

I have verified that a new shared library using ABIVERSION==3 is rejected
by current glibc. The slightly unfortunate effect of this is that ld.so
stops searching for a library when it finds one but fails the ABIVERSION
check. However, the goal is to have the new incompatible ABI extensions
gracefully fail to load and this does happen. 

Thanks,
Matthew

2014-10-02  Matthew Fortune  <matthew.fortune@imgtec.com>

	* elf/elf.h (PT_MIPS_ABIFLAGS): Define.
	(Elf_ABIFlags_v0): New structure.
	(EF_MIPS_FP64): Define.
	(AFL_REG_NONE, AFL_REG_32, AFL_REG_64, AFL_REG_128): Likewise.
	(AFL_ASE_DSP, AFL_ASE_DSP64, AFL_ASE_DSPR2, AFL_ASE_EVA): Likewise.
	(AFL_ASE_MCU, AFL_ASE_MDMX, AFL_ASE_MIPS3D, AFL_ASE_MT): Likewise.
	(AFL_ASE_SMARTMIPS, AFL_ASE_VIRT, AFL_ASE_VIRT64): Likewise.
	(AFL_ASE_MSA, AFL_ASE_MSA64, AFL_ASE_MIPS16): Likewise.
	(AFL_ASE_MICROMIPS, AFL_ASE_XPA): Likewise.
	(AFL_EXT_XLR, AFL_EXT_OCTEON2, AFL_EXT_OCTEONP): Likewise.
	(AFL_EXT_LOONGSON_3A, AFL_EXT_OCTEON, AFL_EXT_5900): Likewise.
	(AFL_EXT_4010, AFL_EXT_4100, AFL_EXT_3900, AFL_EXT_10000): Likewise.
	(AFL_EXT_SB1, AFL_EXT_4111, AFL_EXT_4120, AFL_EXT_5400): Likewise.
	(AFL_EXT_5500, AFL_EXT_LOONGSON_2E, 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/hwcap.h: New file.
	* 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-load-phdr.h: New file.
	* sysdeps/mips/dl-procinfo.c (_dl_mips_cap_flags): Declare.
	* sysdeps/mips/dl-procinfo.h (_DL_HWCAP_COUNT): Define.
	(HWCAP_IMPORTANT): Define.
	(_dl_procinfo): New static inline function.
	(_dl_hwcap_string, _dl_string_hwcap): Likewise.
	* 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/mips/sysdep.h (_SYSDEPS_SYSDEP_H): Define.
	(bits/hwcap.h): Include.
	* 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/hwcap.h                 |  24 +
 sysdeps/mips/bits/linkmap.h               |   2 +
 sysdeps/mips/dl-load-phdr.h               | 280 ++++++++++
 sysdeps/mips/dl-machine.h                 |   5 +
 sysdeps/mips/dl-procinfo.c                |  16 +
 sysdeps/mips/dl-procinfo.h                |  50 +-
 sysdeps/mips/tst-abi-fp32mod.c            |  21 +
 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/mips/sysdep.h                |   3 +
 sysdeps/unix/sysv/linux/mips/configure    | 142 +++++
 sysdeps/unix/sysv/linux/mips/configure.ac |  53 ++
 sysdeps/unix/sysv/linux/mips/ldsodefs.h   |   2 +-
 22 files changed, 2037 insertions(+), 13 deletions(-)
 create mode 100644 sysdeps/mips/bits/hwcap.h
 create mode 100644 sysdeps/mips/dl-load-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

diff --git a/elf/elf.h b/elf/elf.h
index 78815e8..3a3d4fa 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_ABIFlags_v0;
+
+/* Values for the register size bytes of an abi flags structure.  */
+
+#define AFL_REG_NONE		0x00	 /* No registers.  */
+#define AFL_REG_32		0x01	 /* 32-bit registers.  */
+#define AFL_REG_64		0x02	 /* 64-bit registers.  */
+#define AFL_REG_128		0x03	 /* 128-bit registers.  */
+
+/* Masks for the ases word of an ABI flags structure.  */
+
+#define AFL_ASE_DSP		0x00000001 /* DSP ASE.  */
+#define AFL_ASE_DSPR2		0x00000002 /* DSP R2 ASE.  */
+#define AFL_ASE_EVA		0x00000004 /* Enhanced VA Scheme.  */
+#define AFL_ASE_MCU		0x00000008 /* MCU (MicroController) ASE.  */
+#define AFL_ASE_MDMX		0x00000010 /* MDMX ASE.  */
+#define AFL_ASE_MIPS3D		0x00000020 /* MIPS-3D ASE.  */
+#define AFL_ASE_MT		0x00000040 /* MT ASE.  */
+#define AFL_ASE_SMARTMIPS	0x00000080 /* SmartMIPS ASE.  */
+#define AFL_ASE_VIRT		0x00000100 /* VZ ASE.  */
+#define AFL_ASE_MSA		0x00000200 /* MSA ASE.  */
+#define AFL_ASE_MIPS16		0x00000400 /* MIPS16 ASE.  */
+#define AFL_ASE_MICROMIPS	0x00000800 /* MICROMIPS ASE.  */
+#define AFL_ASE_XPA		0x00001000 /* XPA ASE.  */
+
+/* Values for the isa_ext word of an ABI flags structure.  */
+
+#define AFL_EXT_XLR		1   /* RMI Xlr instruction.  */
+#define AFL_EXT_OCTEON2		2   /* Cavium Networks Octeon2.  */
+#define AFL_EXT_OCTEONP		3   /* Cavium Networks OcteonP.  */
+#define AFL_EXT_LOONGSON_3A	4   /* Loongson 3A.  */
+#define AFL_EXT_OCTEON		5   /* Cavium Networks Octeon.  */
+#define AFL_EXT_5900		6   /* MIPS R5900 instruction.  */
+#define AFL_EXT_4650		7   /* MIPS R4650 instruction.  */
+#define AFL_EXT_4010		8   /* LSI R4010 instruction.  */
+#define AFL_EXT_4100		9   /* NEC VR4100 instruction.  */
+#define AFL_EXT_3900		10  /* Toshiba R3900 instruction.  */
+#define AFL_EXT_10000		11  /* MIPS R10000 instruction.  */
+#define AFL_EXT_SB1		12  /* Broadcom SB-1 instruction.  */
+#define AFL_EXT_4111		13  /* NEC VR4111/VR4181 instruction.  */
+#define AFL_EXT_4120		14  /* NEC VR4120 instruction.  */
+#define AFL_EXT_5400		15  /* NEC VR5400 instruction.  */
+#define AFL_EXT_5500		16  /* NEC VR5500 instruction.  */
+#define AFL_EXT_LOONGSON_2E	17  /* ST Microelectronics Loongson 2E.  */
+#define AFL_EXT_LOONGSON_2F	18  /* ST Microelectronics Loongson 2F.  */
+
+/* Masks for the flags1 word of an ABI flags structure.  */
+#define 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/hwcap.h b/sysdeps/mips/bits/hwcap.h
new file mode 100644
index 0000000..0013717
--- /dev/null
+++ b/sysdeps/mips/bits/hwcap.h
@@ -0,0 +1,24 @@
+/* Defines for bits in AT_HWCAP.
+   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/>.  */
+
+#if !defined(_SYS_AUXV_H) && !defined(_SYSDEPS_SYSDEP_H)
+# error "Never include <bits/hwcap.h> directly; use <sys/auxv.h> instead."
+#endif
+
+#define HWCAP_MIPS_R6	0x00000001
+#define HWCAP_MIPS_MSA	0x00000002
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-load-phdr.h b/sysdeps/mips/dl-load-phdr.h
new file mode 100644
index 0000000..ad82b45
--- /dev/null
+++ b/sysdeps/mips/dl-load-phdr.h
@@ -0,0 +1,280 @@
+/* Machine-dependent ELF loader functions.
+   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_LOAD_PHDR_H
+#define _DL_LOAD_PHDR_H
+
+#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;
+}
+
+/* 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)";
+    default:
+      return "Unknown FP ABI";
+    }
+}
+
+/* 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, int fd,
+			   struct link_map *map)
+{
+  const ElfW(Phdr) *ph = find_mips_abiflags (phdr, phnum);
+  struct link_map *l;
+  Lmid_t nsid;
+#if _MIPS_SIM == _ABIO32 && defined __mips_hard_float
+  bool odd_double_required = false;
+  bool cannot_mode_switch = false;
+  unsigned int cur_mode = -1;
+# if HAVE_PRCTL_FP_MODE
+  bool alias_odd_singles = false;
+  unsigned int fr1_mode = PR_FP_MODE_FR;
+# endif
+#endif
+  int req_abi = Val_GNU_MIPS_ABI_FP_DOUBLE;
+  Elf_ABIFlags_v0 *mips_abiflags = NULL;
+
+  /* Read the attributes section.  */
+  if (ph != NULL)
+    {
+      ElfW(Addr) size = ph->p_filesz;
+
+      if (ph->p_offset + size <= len)
+	mips_abiflags = (Elf_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_ABIFlags_v0))
+	{
+	  if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	    _dl_debug_printf ("   contains malformed PT_MIPS_ABIFLAGS\n");
+	  return true;
+	}
+
+      /* Check for MSA support.  */
+      if ((mips_abiflags->ases & 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;
+	}
+
+      req_abi = mips_abiflags->fp_abi;
+    }
+
+  /* ANY is compatible with anything.  */
+  if (req_abi == Val_GNU_MIPS_ABI_FP_ANY)
+    return false;
+
+  /* Check that the new mode 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)
+      {
+	bool success = false;
+	if (l->l_mach.fpabi == 0)
+	  {
+	    l->l_mach.fpabi = Val_GNU_MIPS_ABI_FP_DOUBLE;
+	    l->l_mach.odd_spreg = true;
+	    ph = find_mips_abiflags (l->l_phdr, l->l_phnum);
+	    if (ph)
+	      {
+		if (ph->p_filesz < sizeof (Elf_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_ABIFlags_v0 *) (l->l_addr + ph->p_vaddr);
+		l->l_mach.fpabi = mips_abiflags->fp_abi;
+		l->l_mach.odd_spreg = (mips_abiflags->flags1
+				       & AFL_FLAGS1_ODDSPREG) != 0;
+	      }
+	  }
+
+	/* Found a perfect match, success.  */
+	if (req_abi == l->l_mach.fpabi)
+	  return false;
+
+	/* Modules without an FP ABI are universally compatible.  */
+	if (l->l_mach.fpabi == Val_GNU_MIPS_ABI_FP_ANY)
+	  continue;
+
+	switch (l->l_mach.fpabi)
+	  {
+#if _MIPS_SIM == _ABIO32 && defined __mips_hard_float
+	  /* Compute the current fp mode requirements, and check the required
+	     abi is compatible with the currently loaded objects.  */
+	  case Val_GNU_MIPS_ABI_FP_DOUBLE:
+	    if (req_abi == Val_GNU_MIPS_ABI_FP_XX
+		|| req_abi == Val_GNU_MIPS_ABI_FP_64A)
+	      success = true;
+# if HAVE_PRCTL_FP_MODE
+	    alias_odd_singles = true;
+# endif
+	    break;
+	  case Val_GNU_MIPS_ABI_FP_XX:
+	    if (req_abi == Val_GNU_MIPS_ABI_FP_DOUBLE
+		|| req_abi == Val_GNU_MIPS_ABI_FP_64
+		|| req_abi == Val_GNU_MIPS_ABI_FP_64A)
+	      success = true;
+	    cannot_mode_switch |= l->l_mach.odd_spreg;
+	    break;
+	  case Val_GNU_MIPS_ABI_FP_64A:
+	    if (req_abi == Val_GNU_MIPS_ABI_FP_DOUBLE
+		|| req_abi == Val_GNU_MIPS_ABI_FP_64
+		|| req_abi == Val_GNU_MIPS_ABI_FP_XX)
+	      success = true;
+	    odd_double_required = true;
+	    break;
+	  case Val_GNU_MIPS_ABI_FP_64:
+	    if (req_abi == Val_GNU_MIPS_ABI_FP_XX
+		|| req_abi == Val_GNU_MIPS_ABI_FP_64A)
+	      success = true;
+	    odd_double_required = true;
+	    break;
+#endif
+	  default:
+	    /* Simple 1:1 matches are handled earlier.  Everything else is
+	       a mismatch.  */
+	    success = false;
+	  }
+
+	if (!success)
+	  {
+	    if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+	      _dl_debug_printf ("   uses %s, already loaded %s\n",
+				mips_fp_abi_string (req_abi),
+				mips_fp_abi_string (l->l_mach.fpabi));
+	    return true;
+	  }
+      }
+  /* 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 _MIPS_SIM == _ABIO32 && defined __mips_hard_float
+  /* The loop above has recorded the overall requirements of the already loaded
+     libraries.  Now add in the requirements of the new library.  */
+  if (req_abi == Val_GNU_MIPS_ABI_FP_64A
+      || req_abi == Val_GNU_MIPS_ABI_FP_64)
+    odd_double_required = true;
+
+# if HAVE_PRCTL_FP_MODE
+  if (req_abi == Val_GNU_MIPS_ABI_FP_DOUBLE)
+    alias_odd_singles = true;
+
+  /* The only acceptable FR1 mode which can support alias_odd_single also
+     requires FRE.  */
+  if (alias_odd_singles)
+    fr1_mode |= PR_FP_MODE_FRE;
+
+  /* Get the current hardware mode.  */
+  cur_mode = prctl (PR_GET_FP_MODE);
+# endif
+
+  /* If the PR_GET_FP_MODE is not supported then only FR0 is supported.
+     Odd doubles means FR1 mode so return appropriately.  */
+  if (cur_mode == -1)
+    return odd_double_required;
+
+# 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
+     we don't need odd doubles.  */
+  if (cannot_mode_switch)
+    return cur_mode != fr1_mode
+	   && (odd_double_required || cur_mode != 0);
+
+  /* Set the new mode.  */
+  if (odd_double_required)
+    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
+#endif
+
+  return false;
+}
+
+#endif /* dl-load-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/dl-procinfo.c b/sysdeps/mips/dl-procinfo.c
index 4a3dbf3..3e7b7ed 100644
--- a/sysdeps/mips/dl-procinfo.c
+++ b/sysdeps/mips/dl-procinfo.c
@@ -59,5 +59,21 @@ PROCINFO_CLASS const char _dl_mips_platforms[4][11]
 ,
 #endif
 
+#if !defined PROCINFO_DECL && defined SHARED
+  ._dl_mips_cap_flags
+#else
+PROCINFO_CLASS const char _dl_mips_cap_flags[2][4]
+#endif
+#ifndef PROCINFO_DECL
+= {
+    "r6", "msa"
+  }
+#endif
+#if !defined SHARED || defined PROCINFO_DECL
+;
+#else
+,
+#endif
+
 #undef PROCINFO_DECL
 #undef PROCINFO_CLASS
diff --git a/sysdeps/mips/dl-procinfo.h b/sysdeps/mips/dl-procinfo.h
index b2b7702..208e9d4 100644
--- a/sysdeps/mips/dl-procinfo.h
+++ b/sysdeps/mips/dl-procinfo.h
@@ -50,18 +50,50 @@ _dl_string_platform (const char *str)
   return -1;
 };
 
-/* We cannot provide a general printing function.  */
-#define _dl_procinfo(type, word) -1
+#define _DL_HWCAP_COUNT	2
 
-/* There are no hardware capabilities defined.  */
-#define _dl_hwcap_string(idx) ""
+#define HWCAP_IMPORTANT         (HWCAP_MIPS_MSA)
 
-/* By default there is no important hardware capability.  */
-#define HWCAP_IMPORTANT (0)
+static inline int
+__attribute__ ((unused))
+_dl_procinfo (unsigned int type, unsigned long int word)
+{
+  int i;
+
+  /* Fallback to unknown output mechanism.  */
+  if (type == AT_HWCAP2)
+    return -1;
+
+  _dl_printf ("AT_HWCAP:   ");
+
+  for (i = 0; i < _DL_HWCAP_COUNT; ++i)
+    if (word & (1 << i))
+      _dl_printf (" %s", GLRO(dl_mips_cap_flags)[i]);
+
+  _dl_printf ("\n");
+
+  return 0;
+}
+
+static inline const char *
+__attribute__ ((unused))
+_dl_hwcap_string (int idx)
+{
+  return GLRO(dl_mips_cap_flags)[idx];
+};
 
-/* We don't have any hardware capabilities.  */
-#define _DL_HWCAP_COUNT	0
+static inline int
+__attribute__ ((unused))
+_dl_string_hwcap (const char *str)
+{
+  int i;
 
-#define _dl_string_hwcap(str) (-1)
+  for (i = 0; i < _DL_HWCAP_COUNT; i++)
+    {
+      if (strcmp (str, GLRO(dl_mips_cap_flags)[i]) == 0)
+	return i;
+    }
+  return -1;
+};
 
 #endif /* dl-procinfo.h */
diff --git a/sysdeps/mips/tst-abi-fp32mod.c b/sysdeps/mips/tst-abi-fp32mod.c
new file mode 100644
index 0000000..11c5d49
--- /dev/null
+++ b/sysdeps/mips/tst-abi-fp32mod.c
@@ -0,0 +1,21 @@
+/* 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/mips/sysdep.h b/sysdeps/unix/mips/sysdep.h
index d59fac0..c38f80d 100644
--- a/sysdeps/unix/mips/sysdep.h
+++ b/sysdeps/unix/mips/sysdep.h
@@ -19,6 +19,9 @@
 #include <sgidefs.h>
 #include <sysdeps/unix/sysdep.h>
 
+#define _SYSDEPS_SYSDEP_H 1
+#include <bits/hwcap.h>
+
 #ifdef __ASSEMBLER__
 
 #include <regdef.h>
diff --git a/sysdeps/unix/sysv/linux/mips/configure.ac b/sysdeps/unix/sysv/linux/mips/configure.ac
index c3f217a..9c0a39c 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 <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 */
-- 
1.9.4


[-- Attachment #2: abiflags-v1.tgz --]
[-- Type: application/x-compressed, Size: 16490 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-02 20:07 [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information Matthew Fortune
@ 2014-10-02 20:55 ` Joseph S. Myers
  2014-10-02 22:23   ` Matthew Fortune
  2014-10-07 21:06   ` Matthew Fortune
  0 siblings, 2 replies; 16+ messages in thread
From: Joseph S. Myers @ 2014-10-02 20:55 UTC (permalink / raw)
  To: Matthew Fortune
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

On Thu, 2 Oct 2014, Matthew Fortune wrote:

> == Mixing old and new executables with post-PT_MIPS_ABIFLAGS dynamic linkers ==
> 
> One caveat appears when mixing old soft-float with new soft-float. Since the old
> soft-float binaries had no dynamic linker visible markings then
> post-PT_MIPS_ABIFLAGS logic detects these as hard-float. This means that mixing
> old and new soft-float is not possible and will be rejected. Since soft-float is
> not a publicly supported ABI variant for any major MIPS Linux distribution it
> seems reasonable for this to take the hit of needing a re-build.

No, this should work - meaning that the new logic should accept old 
binaries without PT_MIPS_ABIFLAGS as possibly being either hard or soft 
float.

> == GLIBC tests ==
> 
> A cut down version of the full FPXX testsuite is included in the GLIBC test
> system and covers as many ABI variants as the build configuration allows. The
> glibc version of the test performs load and unload to exhaustively cover the
> double-float ABI variants.

I take it the code will avoid building / running tests not supported in 
the configuration for which glibc is built (if e.g. that's soft-float, or 
with a compiler without FPXX support)?

> +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_ABIFlags_v0;

As this structure in elf.h is MIPS-specific, the name should reflect that, 
rather than having a generic name such as Elf_ABIFlags_v0.

> +/* Values for the register size bytes of an abi flags structure.  */
> +
> +#define AFL_REG_NONE		0x00	 /* No registers.  */
> +#define AFL_REG_32		0x01	 /* 32-bit registers.  */
> +#define AFL_REG_64		0x02	 /* 64-bit registers.  */
> +#define AFL_REG_128		0x03	 /* 128-bit registers.  */

Similarly, MIPS should appear somewhere in all these AFL_* macro names.

> diff --git a/sysdeps/mips/bits/hwcap.h b/sysdeps/mips/bits/hwcap.h
> new file mode 100644
> index 0000000..0013717
> --- /dev/null
> +++ b/sysdeps/mips/bits/hwcap.h

Although small, shouldn't it be possible to split the bits/hwcap.h and 
dl-procinfo changes out to a separate patch?  I don't see anything in them 
that obviously depends on the other changes.

> +#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

"# " indentation for preprocessor directives inside #if.

> +  /* Get the current hardware mode.  */
> +  cur_mode = prctl (PR_GET_FP_MODE);

Is it namespace-safe to call prctl inside the dynamic linker or are there 
circumstances in which it could be overridden by the user's definition of 
prctl (given that prctl is not in the ISO C90 namespace)?  Unless there's 
a clear reason it's safe, __prctl should be used instead.

> +  if (cannot_mode_switch)
> +    return cur_mode != fr1_mode
> +	   && (odd_double_required || cur_mode != 0);

Use parentheses around the return value in such cases as this to help 
ensure proper indentation of the continuation line.

> +int fp32(void)

Please keep to GNU style in testcases, so return type on a separate line 
and space before open parenthesis.

> 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))

I think you should make the static linker set ELFOSABI_GNU for binaries 
with ABI version 3, so that this change isn't needed.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-02 20:55 ` Joseph S. Myers
@ 2014-10-02 22:23   ` Matthew Fortune
  2014-10-07 21:06   ` Matthew Fortune
  1 sibling, 0 replies; 16+ messages in thread
From: Matthew Fortune @ 2014-10-02 22:23 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

Joseph S. Myers <joseph@codesourcery.com> writes:
> On Thu, 2 Oct 2014, Matthew Fortune wrote:
> 
> > == Mixing old and new executables with post-PT_MIPS_ABIFLAGS dynamic
> linkers ==
> >
> > One caveat appears when mixing old soft-float with new soft-float. Since
> the old
> > soft-float binaries had no dynamic linker visible markings then
> > post-PT_MIPS_ABIFLAGS logic detects these as hard-float. This means that
> mixing
> > old and new soft-float is not possible and will be rejected. Since soft-
> float is
> > not a publicly supported ABI variant for any major MIPS Linux
> distribution it
> > seems reasonable for this to take the hit of needing a re-build.
> 
> No, this should work - meaning that the new logic should accept old
> binaries without PT_MIPS_ABIFLAGS as possibly being either hard or soft
> float.

I'll have to consider the impact of this on the overall plan and also what
it will do to implementation. It is perhaps OK but I have some concerns over
how to implement it. I.e. I am very much against considering existing
libraries to be the 'any' ABI but will look into them being either soft-float
(fpabi 3) or double-precision (fpabi 1).

> > == GLIBC tests ==
> >
> > A cut down version of the full FPXX testsuite is included in the GLIBC
> test
> > system and covers as many ABI variants as the build configuration allows.
> The
> > glibc version of the test performs load and unload to exhaustively cover
> the
> > double-float ABI variants.
> 
> I take it the code will avoid building / running tests not supported in
> the configuration for which glibc is built (if e.g. that's soft-float, or
> with a compiler without FPXX support)?

That is the intention. The configure checks are there to determine what the
precise O32 FP ABI is and from that the test case may or may not be enabled
and the test libraries are built depending on what can successfully link.

> > +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_ABIFlags_v0;
> 
> As this structure in elf.h is MIPS-specific, the name should reflect that,
> rather than having a generic name such as Elf_ABIFlags_v0.

Indeed. I simply copy/pasted this from binutils but binutils segregates
things more into architectures. I'll make it Elf_MIPS_ABIFlags_v0.

> > +/* Values for the register size bytes of an abi flags structure.  */
> > +
> > +#define AFL_REG_NONE		0x00	 /* No registers.  */
> > +#define AFL_REG_32		0x01	 /* 32-bit registers.  */
> > +#define AFL_REG_64		0x02	 /* 64-bit registers.  */
> > +#define AFL_REG_128		0x03	 /* 128-bit registers.  */
> 
> Similarly, MIPS should appear somewhere in all these AFL_* macro names.

Prefixing with MIPS_ throughout would seem OK here.

> > diff --git a/sysdeps/mips/bits/hwcap.h b/sysdeps/mips/bits/hwcap.h
> > new file mode 100644
> > index 0000000..0013717
> > --- /dev/null
> > +++ b/sysdeps/mips/bits/hwcap.h
> 
> Although small, shouldn't it be possible to split the bits/hwcap.h and
> dl-procinfo changes out to a separate patch?  I don't see anything in them
> that obviously depends on the other changes.

Will do.

> > +  /* Get the current hardware mode.  */
> > +  cur_mode = prctl (PR_GET_FP_MODE);
> 
> Is it namespace-safe to call prctl inside the dynamic linker or are there
> circumstances in which it could be overridden by the user's definition of
> prctl (given that prctl is not in the ISO C90 namespace)?  Unless there's
> a clear reason it's safe, __prctl should be used instead.

My honest answer is that I don't know so I will switch to __prctl regardless.
 
> > 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))
> 
> I think you should make the static linker set ELFOSABI_GNU for binaries
> with ABI version 3, so that this change isn't needed.

I'm not sure that I am OK with this. The ABI extension is not a GNU specific
extension, it is a generic ABI extension which is why I have applied it to
both osabis with the same value.

Thanks for the quick feedback, I'll wait a day or so before posting an update
to allow anyone else to comment.

Matthew

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-02 20:55 ` Joseph S. Myers
  2014-10-02 22:23   ` Matthew Fortune
@ 2014-10-07 21:06   ` Matthew Fortune
  2014-10-07 21:10     ` Joseph S. Myers
  1 sibling, 1 reply; 16+ messages in thread
From: Matthew Fortune @ 2014-10-07 21:06 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

Joseph S. Myers <joseph@codesourcery.com>  writes:
> On Thu, 2 Oct 2014, Matthew Fortune wrote:
> 
> > == Mixing old and new executables with post-PT_MIPS_ABIFLAGS dynamic
> linkers ==
> >
> > One caveat appears when mixing old soft-float with new soft-float. Since
> the old
> > soft-float binaries had no dynamic linker visible markings then
> > post-PT_MIPS_ABIFLAGS logic detects these as hard-float. This means that
> mixing
> > old and new soft-float is not possible and will be rejected. Since soft-
> float is
> > not a publicly supported ABI variant for any major MIPS Linux distribution
> it
> > seems reasonable for this to take the hit of needing a re-build.
> 
> No, this should work - meaning that the new logic should accept old
> binaries without PT_MIPS_ABIFLAGS as possibly being either hard or soft
> float.

Just checking your opinion here. I'm relaxing the linkage rules for
non-PT_GNU_MIPS binaries such that they can link with SOFT|DOUBLE|FPXX|FP64A
but strictly not FP64. I am on the fence as to whether I should allow SINGLE
as well. Strictly speaking there could be GNU systems with single-float but
I don't think GLIBC supports single-float so it is unlikely that there are any
non-PT_GNU_MIPS binaries out there using single-float. Every special case
costs extra code complexity and test coverage so if it is not important I'd
rather leave it.

What do you think?

Matthew

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-07 21:06   ` Matthew Fortune
@ 2014-10-07 21:10     ` Joseph S. Myers
  2014-10-17 20:37       ` Matthew Fortune
  0 siblings, 1 reply; 16+ messages in thread
From: Joseph S. Myers @ 2014-10-07 21:10 UTC (permalink / raw)
  To: Matthew Fortune
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

On Tue, 7 Oct 2014, Matthew Fortune wrote:

> Just checking your opinion here. I'm relaxing the linkage rules for
> non-PT_GNU_MIPS binaries such that they can link with SOFT|DOUBLE|FPXX|FP64A
> but strictly not FP64. I am on the fence as to whether I should allow SINGLE
> as well. Strictly speaking there could be GNU systems with single-float but
> I don't think GLIBC supports single-float so it is unlikely that there are any
> non-PT_GNU_MIPS binaries out there using single-float. Every special case
> costs extra code complexity and test coverage so if it is not important I'd
> rather leave it.

glibc does not support single-float; see the list of known ABIs at 
<https://sourceware.org/glibc/wiki/ABIList>.  So there is no need for the 
glibc code to allow for it.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-07 21:10     ` Joseph S. Myers
@ 2014-10-17 20:37       ` Matthew Fortune
  2014-10-17 20:52         ` Joseph S. Myers
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Fortune @ 2014-10-17 20:37 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

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

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 */
-- 
1.9.4

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-17 20:37       ` Matthew Fortune
@ 2014-10-17 20:52         ` Joseph S. Myers
  2014-10-19  7:30           ` Matthew Fortune
  0 siblings, 1 reply; 16+ messages in thread
From: Joseph S. Myers @ 2014-10-17 20:52 UTC (permalink / raw)
  To: Matthew Fortune
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

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.)

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-17 20:52         ` Joseph S. Myers
@ 2014-10-19  7:30           ` Matthew Fortune
  2014-10-19 15:43             ` Joseph S. Myers
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Fortune @ 2014-10-19  7:30 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

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

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-19  7:30           ` Matthew Fortune
@ 2014-10-19 15:43             ` Joseph S. Myers
  2014-10-20  8:27               ` Matthew Fortune
  0 siblings, 1 reply; 16+ messages in thread
From: Joseph S. Myers @ 2014-10-19 15:43 UTC (permalink / raw)
  To: Matthew Fortune
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

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?

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-19 15:43             ` Joseph S. Myers
@ 2014-10-20  8:27               ` Matthew Fortune
  2014-10-28 21:50                 ` Matthew Fortune
  2014-10-31  0:36                 ` Joseph S. Myers
  0 siblings, 2 replies; 16+ messages in thread
From: Matthew Fortune @ 2014-10-20  8:27 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

Joseph S. Myers <joseph@codesourcery.com>writes:
> 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?

I see what you mean! It appears I attached an intermediate version of the
rework. Re-attached below, apologies for wasting your time reading through
the copy I posted.
 
> > 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?

I'm interested in an architecture-independent view on this but I'm not sure
that it needs to be a concept accepted by all architectures to allow it
to be used for MIPS. (I have not yet started a larger debate about this.)

Some rationale for MIPS:

*) Current lack of ifunc-esque features in MIPS means that we don't have
   any definition of what an object will look like to support such a thing.
   What we do have today is a description of what is used somewhere in an
   executable and therefore will be called at some point. MIPS also has no
   kernel-user interfaces defined to probe what hardware features are
   available so the likelihood of home-brew ifunc-esque logic is low.
*) Given the way R6 is heading it is not beyond reason that future arch
   revisions will both explicitly not support old ASEs and eventually
   reuse the opcode space).
   For this to work any new ASE reusing opcode space would not be allowed
   to link against anything which overlaps with it.
*) The actual link checks are really just a safety measure for what is
   already supported via the HWCAPS_IMPORTANT logic so it is not much of
   a stretch from today's features.
*) If we don't invent some new linkage rules then the only mechanism we
   have for introducing new incompatible extensions is the ABIVERSION
   logic. This falls short of providing full safety (at least for now)
   as it only checks the version of objects being loaded and not the
   original executable. If this loophole were closed then ABIVERSION
   could be used whenever an incompatible extension were used/added. More
   on this in the libc-abis thread... However, since MIPS will have
   information available in the MIPS.abiflags section it seems slightly
   redundant to have to continuously bump ABIVERSION when introducing new
   flags/values. (I guess in reality the e_flag for NAN2008 should have
   bumped the ABIVERSION for that to be really safe).
*) As a configurable and extensible architecture, MIPS implementations have
   many variations and actively detecting (and selectively linking) code
   is likely to give a more stable user environment. Unlike the platform
   level of search paths this is about correctness for a platform rather
   than just optimisation.
*) All the above is centered around ASEs but actually the same issues
   exist for implementation/platform specific extensions. I considered
   adding the linkage rules to (e.g.) prevent loading an octeon binary on
   something which did not have octeon instructions but decided to leave
   that to future work (I.e. the patch only deals with core architecture
   issues). The platform specific issues can be addressed using the
   platform strings rather than HWCAPs of course.
*) Sparc and s390_32 both already use HWCAPS as part of the pre-existing
   elf_machine_matches_host check so there is some precedence already.

For the flags2 and undefined ASE checks I can actually remove them and
add them alongside a further ABIVERSION increase in the future. That
may in fact make some sense so that the MIPS_O32_FP64 ABIVERSION is not
overloaded to handle multiple issues. I cannot remove the MSA ASE check
in this way as it is already tied to the MIPS_O32_FP64 ABIVERSION. Would
that help to move this work forward?

Apologies again for posting the wrong version of the patch.

Thanks,
Matthew

2014-10-19  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                                 | 101 +++-
 libc-abis                                 |   3 +
 sysdeps/mips/Makefile                     |  50 ++
 sysdeps/mips/bits/linkmap.h               |   2 +
 sysdeps/mips/dl-machine-reject-phdr.h     | 342 ++++++++++++
 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, 2017 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

diff --git a/elf/elf.h b/elf/elf.h
index 78815e8..8e6f979 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,99 @@ 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.  */
+#define MIPS_AFL_ASE_MASK	0x00001fff /* All ASEs.  */
+
+/* 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..444fe44
--- /dev/null
+++ b/sysdeps/mips/dl-machine-reject-phdr.h
@@ -0,0 +1,342 @@
+/* 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
+
+/* Reject an object with a debug message.  */
+#define REJECT(str, args...)						      \
+  {									      \
+    if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))		      \
+      _dl_debug_printf (str, ##args);					      \
+    return true;							      \
+  }
+
+/* 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 bool
+cached_fpabi_reject_phdr_p (struct link_map *l)
+{
+  if (l->l_mach.fpabi == 0)
+    {
+      const ElfW(Phdr) *ph = find_mips_abiflags (l->l_phdr, l->l_phnum);
+
+      if (ph)
+	{
+	  Elf_MIPS_ABIFlags_v0 * mips_abiflags;
+	  if (ph->p_filesz < sizeof (Elf_MIPS_ABIFlags_v0))
+	    REJECT ("   %s: malformed PT_MIPS_ABIFLAGS found\n", l->l_name);
+
+	  mips_abiflags = (Elf_MIPS_ABIFlags_v0 *) (l->l_addr + ph->p_vaddr);
+
+	  if (__glibc_unlikely (mips_abiflags->flags2 != 0))
+	    REJECT ("   %s: unknown MIPS.abiflags flags2: %u\n", l->l_name,
+		    mips_abiflags->flags2);
+
+	  /* Check for MSA support.  This test should be updated when an MSA
+	     HWCAP is defined.  */
+	  if ((mips_abiflags->ases & MIPS_AFL_ASE_MSA) != 0)
+	    REJECT ("   %s: requires MSA support\n", l->l_name);
+
+	  if (__glibc_unlikely ((mips_abiflags->ases
+				 & ~MIPS_AFL_ASE_MASK) != 0))
+	    REJECT ("   %s: requires unknown ASE: %x\n", l->l_name,
+		    mips_abiflags->ases);
+
+	  l->l_mach.fpabi = mips_abiflags->fp_abi;
+	  l->l_mach.odd_spreg = (mips_abiflags->flags1
+				 & MIPS_AFL_FLAGS1_ODDSPREG) != 0;
+	}
+      else
+	{
+	  l->l_mach.fpabi = -1;
+	  l->l_mach.odd_spreg = true;
+	}
+    }
+  return false;
+}
+
+/* Return a description of the specified floating-point ABI.  */
+
+static const char *
+fpabi_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 float";
+    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},  /* Any */
+     {false, false, true,  false, true},  /* Double-float */
+     {true,  false, false, false, false}, /* Single-float */
+     {false, true,  false, false, false}, /* Soft-float */
+     {false, false, false, false, false}, /* old-FP64 */
+     {false, false, true,  true,  true},  /* FPXX */
+     {false, false, false, true,  false}, /* FP64 */
+     {false, false, false, true,  true}}; /* FP64A */
+
+/* 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)
+	    REJECT ("   unable to read PT_MIPS_ABIFLAGS\n");
+	}
+
+      if (size < sizeof (Elf_MIPS_ABIFlags_v0))
+	REJECT ("   contains malformed PT_MIPS_ABIFLAGS\n");
+
+      if (__glibc_unlikely (mips_abiflags->flags2 != 0))
+	REJECT ("   unknown MIPS.abiflags flags2: %u\n", mips_abiflags->flags2);
+
+      /* Check for MSA support.  This test should be updated when an MSA
+	 HWCAP is defined.  */
+      if ((mips_abiflags->ases & MIPS_AFL_ASE_MSA) != 0)
+	REJECT ("   requires MSA support\n");
+
+      /* Check for unknown ASE usage.  */
+      if (__glibc_unlikely ((mips_abiflags->ases & ~MIPS_AFL_ASE_MASK) != 0))
+	REJECT ("   requires unknown ASE: %x\n", mips_abiflags->ases);
+
+      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)
+    REJECT ("   uses unknown FP ABI: %u\n", in_abi);
+
+  /* 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;
+
+	if (cached_fpabi_reject_phdr_p (l))
+	  return true;
+
+#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 linked 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)
+	  REJECT ("   found %s running in the wrong mode\n",
+		  fpabi_string (l->l_mach.fpabi));
+#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)
+	  REJECT ("   found unknown FP ABI: %u\n", l->l_mach.fpabi);
+
+	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
+	  REJECT ("   uses %s, already loaded %s\n",
+		  fpabi_string (in_abi),
+		  fpabi_string (l->l_mach.fpabi));
+      }
+
+#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 */
-- 
1.9.4

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-20  8:27               ` Matthew Fortune
@ 2014-10-28 21:50                 ` Matthew Fortune
  2014-10-31  0:36                 ` Joseph S. Myers
  1 sibling, 0 replies; 16+ messages in thread
From: Matthew Fortune @ 2014-10-28 21:50 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

Matthew Fortune <matthew.fortune@imgtec.com>
> in this way as it is already tied to the MIPS_O32_FP64 ABIVERSION. Would
> that help to move this work forward?

Just checking we are not waiting on each other for information...

FWIW I don't really want to present another version of this patch without
a concrete definition of what will be accepted as it takes a significant
time to prepare and retest.

Thanks,
Matthew

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-20  8:27               ` Matthew Fortune
  2014-10-28 21:50                 ` Matthew Fortune
@ 2014-10-31  0:36                 ` Joseph S. Myers
  2014-10-31 10:21                   ` Matthew Fortune
  1 sibling, 1 reply; 16+ messages in thread
From: Joseph S. Myers @ 2014-10-31  0:36 UTC (permalink / raw)
  To: Matthew Fortune
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

On Mon, 20 Oct 2014, Matthew Fortune wrote:

> For the flags2 and undefined ASE checks I can actually remove them and
> add them alongside a further ABIVERSION increase in the future. That

For undefined ASEs, I think it's a mistake to test for them in glibc.

The normal assumption is that minimal hardware enablement for new 
processor features may need a new kernel (after all, the kernel needs to 
handle the new registers on context switch, if nothing else) but shouldn't 
need new glibc binaries (this is why new registers should be 
call-clobbered); given a new kernel, you should be able to use the new 
instructions with existing glibc binaries on your new hardware.

So you might assume that use of new instructions means a kernel with 
support for them (and thus a kernel that sets the relevant HWCAP bit), but 
not a glibc with support for them (so addition of checks in future for 
HWCAP bits for new ASEs adds safety, but from a starting point where old 
glibc will run binaries with the new instructions, just without the safety 
check that the ASE is in fact supported).

As for flags2 - the ABI document seems silent on how to tell whether a new 
flag is compatible with old dynamic linkers (i.e., whether it's safe to 
ignore it).  This is of course a common problem with other interfaces 
(various Linux kernel interfaces quietly ignore unknown flag bits, causing 
problems for programs that then need to check whether the flag they passed 
in was handled properly).  I suppose you could define parts of the flag 
space as being safe to ignore and other parts as not being safe to ignore 
(if necessary, you can always move bits from not-safe-to-ignore to 
safe-to-ignore in future, at the cost of needing a more recent dynamic 
linker to handle binaries setting those bits).  Is the design here that 
flags2 is not-safe-to-ignore but flags1 is safe-to-ignore?  That appears 
to be how this patch is using the bits (even though the one bit defined 
and tested for in flags1 seems to be to fall into not-safe-to-ignore).

(I also still think that if you have MSA HWCAP bits there should at least 
be a compiler / assembler / linker option to build a binary that uses MSA 
but is not marked as doing so, for use when doing dynamic detection of MSA 
availability - this shouldn't only be available through "target" 
attributes to compile individual functions with MSA.)

> +  /* Unknown ABIs are rejected.  */
> +  if (in_abi != -1 && in_abi > Val_GNU_MIPS_ABI_FP_64A)
> +    REJECT ("   uses unknown FP ABI: %u\n", in_abi);

I think it would be better for the header to have a name such as 
Val_GNU_MIPS_ABI_FP_MAX rather than hardcoding the information here that 
Val_GNU_MIPS_ABI_FP_64A is, of the ABIs known to glibc, the one with the 
highest number.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-31  0:36                 ` Joseph S. Myers
@ 2014-10-31 10:21                   ` Matthew Fortune
  2014-10-31 13:52                     ` Joseph S. Myers
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Fortune @ 2014-10-31 10:21 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

Joseph S. Myers <joseph@codesourcery.com> writes:
> On Mon, 20 Oct 2014, Matthew Fortune wrote:
> 
> > For the flags2 and undefined ASE checks I can actually remove them and
> > add them alongside a further ABIVERSION increase in the future. That
> 
> For undefined ASEs, I think it's a mistake to test for them in glibc.

OK. I am perhaps being over-cautious owing to the underlying floating-point
mode issues and wanting to try my best to protect against future ASEs having
some form of modal requirement.

> The normal assumption is that minimal hardware enablement for new
> processor features may need a new kernel (after all, the kernel needs to
> handle the new registers on context switch, if nothing else) but shouldn't
> need new glibc binaries (this is why new registers should be
> call-clobbered);

Agreed. I'm taking the same stance when discussing architecture extensions
internally and also on any other tools discussion. I think the S390 problems
have raised awareness of this issue for the whole community.

> given a new kernel, you should be able to use the new
> instructions with existing glibc binaries on your new hardware.
> 
> So you might assume that use of new instructions means a kernel with
> support for them (and thus a kernel that sets the relevant HWCAP bit), but
> not a glibc with support for them (so addition of checks in future for
> HWCAP bits for new ASEs adds safety, but from a starting point where old
> glibc will run binaries with the new instructions, just without the safety
> check that the ASE is in fact supported).

I'm happy to work on that basis if you are OK in principle at least with
adding safety checks as and when we have HWCAPs defined for protection.

> As for flags2 - the ABI document seems silent on how to tell whether a new
> flag is compatible with old dynamic linkers (i.e., whether it's safe to
> ignore it).  This is of course a common problem with other interfaces
> (various Linux kernel interfaces quietly ignore unknown flag bits, causing
> problems for programs that then need to check whether the flag they passed
> in was handled properly).  I suppose you could define parts of the flag
> space as being safe to ignore and other parts as not being safe to ignore
> (if necessary, you can always move bits from not-safe-to-ignore to
> safe-to-ignore in future, at the cost of needing a more recent dynamic
> linker to handle binaries setting those bits).  Is the design here that
> flags2 is not-safe-to-ignore but flags1 is safe-to-ignore?  That appears

I was trying to achieve that yes. I guess the ODDSPREG flag should really
have gone in to flags2 given it was not just informational but since it
was introduced with the initial specification of .MIPS.abiflags then it
doesn't really make much difference. If we were to have added such a flag
after the initial version then it would definitely have gone in flags2.
Flags1 is also defined to be a simple bitwise-OR of all input objects where
flags2 is expected to have custom rules for any bits which get defined.

> to be how this patch is using the bits (even though the one bit defined
> and tested for in flags1 seems to be to fall into not-safe-to-ignore).
> 
> (I also still think that if you have MSA HWCAP bits there should at least
> be a compiler / assembler / linker option to build a binary that uses MSA
> but is not marked as doing so, for use when doing dynamic detection of MSA
> availability - this shouldn't only be available through "target"
> attributes to compile individual functions with MSA.)

I fully intend to make that work. My intention is to specify that as part
of ifunc work. From an assembler level this should be OK already but from
the compiler there is work to do, the FP mode requirements complicate it
as well.

> > +  /* Unknown ABIs are rejected.  */
> > +  if (in_abi != -1 && in_abi > Val_GNU_MIPS_ABI_FP_64A)
> > +    REJECT ("   uses unknown FP ABI: %u\n", in_abi);
> 
> I think it would be better for the header to have a name such as
> Val_GNU_MIPS_ABI_FP_MAX rather than hardcoding the information here that
> Val_GNU_MIPS_ABI_FP_64A is, of the ABIs known to glibc, the one with the
> highest number.

I can do that. I was concerned about someone updating the list of FP ABIs
but not updating the table in dl-machine-reject-phdr.h and it leading to 
some strange runtime crashes. If I just size the 'reqs' table using
Val_GNU_MIPS_ABI_FP_MAX then any future ABI extension will get a zero
initialised entry which would be ideal.

So the plan is:

* Remove any ASE related checks from this patch
* Allow ASE checks to be added when there are HWCAPs defined
* Leave the flags2 check as a way of avoiding bumping the ABIVERSION
  for future ABI changes. (Not that I'm in any rush to try modifying
  the MIPS ABIs again!)
* If a future ASE needs some special handling which can't be dealt with
  via flags2 then that would need an ABIVERSION bump.

Do you think that covers it?

Thanks,
Matthew

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-31 10:21                   ` Matthew Fortune
@ 2014-10-31 13:52                     ` Joseph S. Myers
  2014-10-31 15:20                       ` Matthew Fortune
  0 siblings, 1 reply; 16+ messages in thread
From: Joseph S. Myers @ 2014-10-31 13:52 UTC (permalink / raw)
  To: Matthew Fortune
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

On Fri, 31 Oct 2014, Matthew Fortune wrote:

> > So you might assume that use of new instructions means a kernel with
> > support for them (and thus a kernel that sets the relevant HWCAP bit), but
> > not a glibc with support for them (so addition of checks in future for
> > HWCAP bits for new ASEs adds safety, but from a starting point where old
> > glibc will run binaries with the new instructions, just without the safety
> > check that the ASE is in fact supported).
> 
> I'm happy to work on that basis if you are OK in principle at least with
> adding safety checks as and when we have HWCAPs defined for protection.

OK in principle (though I believe we'll want a way to disable those checks 
at compile / link time of a binary that uses ASEs with runtime 
conditionals).

> So the plan is:
> 
> * Remove any ASE related checks from this patch
> * Allow ASE checks to be added when there are HWCAPs defined
> * Leave the flags2 check as a way of avoiding bumping the ABIVERSION
>   for future ABI changes. (Not that I'm in any rush to try modifying
>   the MIPS ABIs again!)

* Update the ABI document to state how unknown bits in flags1 and flags2 
should be handled (ignored, and cause the binary to be rejected, 
respectively).

> * If a future ASE needs some special handling which can't be dealt with
>   via flags2 then that would need an ABIVERSION bump.
> 
> Do you think that covers it?

* libc-abis should follow the direction in 
<https://sourceware.org/ml/libc-alpha/2014-10/msg00578.html> (meaning you 
add to the MIPS file, rather than being relative to a tree with a patch 
merging the files applied).

* As <https://sourceware.org/ml/libc-alpha/2014-10/msg00352.html> was 
approved it should be committed so the patch doesn't need to be relative 
to a tree with another uncommitted patch applied either.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-31 13:52                     ` Joseph S. Myers
@ 2014-10-31 15:20                       ` Matthew Fortune
  2014-10-31 16:34                         ` Joseph S. Myers
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Fortune @ 2014-10-31 15:20 UTC (permalink / raw)
  To: Joseph S. Myers
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

Joseph S. Myers <joseph@codesourcery.com> writes:
> On Fri, 31 Oct 2014, Matthew Fortune wrote:
> 
> > > So you might assume that use of new instructions means a kernel with
> > > support for them (and thus a kernel that sets the relevant HWCAP bit),
> but
> > > not a glibc with support for them (so addition of checks in future for
> > > HWCAP bits for new ASEs adds safety, but from a starting point where
> old
> > > glibc will run binaries with the new instructions, just without the
> safety
> > > check that the ASE is in fact supported).
> >
> > I'm happy to work on that basis if you are OK in principle at least with
> > adding safety checks as and when we have HWCAPs defined for protection.
> 
> OK in principle (though I believe we'll want a way to disable those checks
> at compile / link time of a binary that uses ASEs with runtime
> conditionals).
> 
> > So the plan is:
> >
> > * Remove any ASE related checks from this patch
> > * Allow ASE checks to be added when there are HWCAPs defined
> > * Leave the flags2 check as a way of avoiding bumping the ABIVERSION
> >   for future ABI changes. (Not that I'm in any rush to try modifying
> >   the MIPS ABIs again!)
> 
> * Update the ABI document to state how unknown bits in flags1 and flags2
> should be handled (ignored, and cause the binary to be rejected,
> respectively).
> 
> > * If a future ASE needs some special handling which can't be dealt with
> >   via flags2 then that would need an ABIVERSION bump.
> >
> > Do you think that covers it?
> 
> * libc-abis should follow the direction in
> <https://sourceware.org/ml/libc-alpha/2014-10/msg00578.html> (meaning you
> add to the MIPS file, rather than being relative to a tree with a patch
> merging the files applied).
> 
> * As <https://sourceware.org/ml/libc-alpha/2014-10/msg00352.html> was
> approved it should be committed so the patch doesn't need to be relative
> to a tree with another uncommitted patch applied either.

That's fine. I was holding off just in case the discussion on this patch
ended up changing the hook.

Am I alright to request commit access to glibc with you as reference?

Thanks,
Matthew

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information
  2014-10-31 15:20                       ` Matthew Fortune
@ 2014-10-31 16:34                         ` Joseph S. Myers
  0 siblings, 0 replies; 16+ messages in thread
From: Joseph S. Myers @ 2014-10-31 16:34 UTC (permalink / raw)
  To: Matthew Fortune
  Cc: 'libc-alpha@sourceware.org',
	Moore, Catherine (Catherine_Moore@mentor.com),
	'Andrew Pinski', Rich Felker (dalias@libc.org),
	Rich Fuhler, 'macro@codesourcery.com'

On Fri, 31 Oct 2014, Matthew Fortune wrote:

> Am I alright to request commit access to glibc with you as reference?

Yes.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2014-10-31 16:34 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-10-02 20:07 [PATCHv3, MIPS] Add support for O32 FPXX and program header based ABI information Matthew Fortune
2014-10-02 20:55 ` Joseph S. Myers
2014-10-02 22:23   ` Matthew Fortune
2014-10-07 21:06   ` Matthew Fortune
2014-10-07 21:10     ` Joseph S. Myers
2014-10-17 20:37       ` Matthew Fortune
2014-10-17 20:52         ` Joseph S. Myers
2014-10-19  7:30           ` Matthew Fortune
2014-10-19 15:43             ` Joseph S. Myers
2014-10-20  8:27               ` Matthew Fortune
2014-10-28 21:50                 ` Matthew Fortune
2014-10-31  0:36                 ` Joseph S. Myers
2014-10-31 10:21                   ` Matthew Fortune
2014-10-31 13:52                     ` Joseph S. Myers
2014-10-31 15:20                       ` Matthew Fortune
2014-10-31 16:34                         ` Joseph S. Myers

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).