This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

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


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: abiflags-v1.tgz
Description: abiflags-v1.tgz


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]