summaryrefslogtreecommitdiff
path: root/arch/mips/kernel/process.c
diff options
context:
space:
mode:
authorPaul Burton <paul.burton@imgtec.com>2015-01-08 12:17:37 +0000
committerRalf Baechle <ralf@linux-mips.org>2015-02-12 12:30:29 +0100
commit9791554b45a2acc28247f66a5fd5bbc212a6b8c8 (patch)
treeb23e43745d5b5f10b004dcbe216b5a02ad6018ec /arch/mips/kernel/process.c
parentae58d882bfd3e537b1ed4a4c3577ca9ba853f0d8 (diff)
MIPS,prctl: add PR_[GS]ET_FP_MODE prctl options for MIPS
Userland code may be built using an ABI which permits linking to objects that have more restrictive floating point requirements. For example, userland code may be built to target the O32 FPXX ABI. Such code may be linked with other FPXX code, or code built for either one of the more restrictive FP32 or FP64. When linking with more restrictive code, the overall requirement of the process becomes that of the more restrictive code. The kernel has no way to know in advance which mode the process will need to be executed in, and indeed it may need to change during execution. The dynamic loader is the only code which will know the overall required mode, and so it needs to have a means to instruct the kernel to switch the FP mode of the process. This patch introduces 2 new options to the prctl syscall which provide such a capability. The FP mode of the process is represented as a simple bitmask combining a number of mode bits mirroring those present in the hardware. Userland can either retrieve the current FP mode of the process: mode = prctl(PR_GET_FP_MODE); or modify the current FP mode of the process: err = prctl(PR_SET_FP_MODE, new_mode); Signed-off-by: Paul Burton <paul.burton@imgtec.com> Cc: Matthew Fortune <matthew.fortune@imgtec.com> Cc: Markos Chandras <markos.chandras@imgtec.com> Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/8899/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/kernel/process.c')
-rw-r--r--arch/mips/kernel/process.c92
1 files changed, 92 insertions, 0 deletions
diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c
index eb76434828e8..4677b4c67da6 100644
--- a/arch/mips/kernel/process.c
+++ b/arch/mips/kernel/process.c
@@ -25,6 +25,7 @@
#include <linux/completion.h>
#include <linux/kallsyms.h>
#include <linux/random.h>
+#include <linux/prctl.h>
#include <asm/asm.h>
#include <asm/bootinfo.h>
@@ -550,3 +551,94 @@ void arch_trigger_all_cpu_backtrace(bool include_self)
{
smp_call_function(arch_dump_stack, NULL, 1);
}
+
+int mips_get_process_fp_mode(struct task_struct *task)
+{
+ int value = 0;
+
+ if (!test_tsk_thread_flag(task, TIF_32BIT_FPREGS))
+ value |= PR_FP_MODE_FR;
+ if (test_tsk_thread_flag(task, TIF_HYBRID_FPREGS))
+ value |= PR_FP_MODE_FRE;
+
+ return value;
+}
+
+int mips_set_process_fp_mode(struct task_struct *task, unsigned int value)
+{
+ const unsigned int known_bits = PR_FP_MODE_FR | PR_FP_MODE_FRE;
+ unsigned long switch_count;
+ struct task_struct *t;
+
+ /* Check the value is valid */
+ if (value & ~known_bits)
+ return -EOPNOTSUPP;
+
+ /* Avoid inadvertently triggering emulation */
+ if ((value & PR_FP_MODE_FR) && cpu_has_fpu &&
+ !(current_cpu_data.fpu_id & MIPS_FPIR_F64))
+ return -EOPNOTSUPP;
+ if ((value & PR_FP_MODE_FRE) && cpu_has_fpu && !cpu_has_fre)
+ return -EOPNOTSUPP;
+
+ /* Save FP & vector context, then disable FPU & MSA */
+ if (task->signal == current->signal)
+ lose_fpu(1);
+
+ /* Prevent any threads from obtaining live FP context */
+ atomic_set(&task->mm->context.fp_mode_switching, 1);
+ smp_mb__after_atomic();
+
+ /*
+ * If there are multiple online CPUs then wait until all threads whose
+ * FP mode is about to change have been context switched. This approach
+ * allows us to only worry about whether an FP mode switch is in
+ * progress when FP is first used in a tasks time slice. Pretty much all
+ * of the mode switch overhead can thus be confined to cases where mode
+ * switches are actually occuring. That is, to here. However for the
+ * thread performing the mode switch it may take a while...
+ */
+ if (num_online_cpus() > 1) {
+ spin_lock_irq(&task->sighand->siglock);
+
+ for_each_thread(task, t) {
+ if (t == current)
+ continue;
+
+ switch_count = t->nvcsw + t->nivcsw;
+
+ do {
+ spin_unlock_irq(&task->sighand->siglock);
+ cond_resched();
+ spin_lock_irq(&task->sighand->siglock);
+ } while ((t->nvcsw + t->nivcsw) == switch_count);
+ }
+
+ spin_unlock_irq(&task->sighand->siglock);
+ }
+
+ /*
+ * There are now no threads of the process with live FP context, so it
+ * is safe to proceed with the FP mode switch.
+ */
+ for_each_thread(task, t) {
+ /* Update desired FP register width */
+ if (value & PR_FP_MODE_FR) {
+ clear_tsk_thread_flag(t, TIF_32BIT_FPREGS);
+ } else {
+ set_tsk_thread_flag(t, TIF_32BIT_FPREGS);
+ clear_tsk_thread_flag(t, TIF_MSA_CTX_LIVE);
+ }
+
+ /* Update desired FP single layout */
+ if (value & PR_FP_MODE_FRE)
+ set_tsk_thread_flag(t, TIF_HYBRID_FPREGS);
+ else
+ clear_tsk_thread_flag(t, TIF_HYBRID_FPREGS);
+ }
+
+ /* Allow threads to use FP again */
+ atomic_set(&task->mm->context.fp_mode_switching, 0);
+
+ return 0;
+}