Re: [PATCH] MIPS,prctl: add PR_[GS]ET_FP_MODE prctl options for MIPS

From: Markos Chandras
Date: Tue Jan 13 2015 - 08:12:40 EST


On 01/08/2015 12:17 PM, Paul Burton wrote:
> 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@xxxxxxxxxx>
> Cc: Matthew Fortune <matthew.fortune@xxxxxxxxxx>
> Cc: Markos Chandras <markos.chandras@xxxxxxxxxx>
Hi,

I think the "MIPS,prctl" in the title should be "MIPS: prctl"

I have also CC'd the LKML and the linux-api mailing lists since this
touches the kernel ABI with the new PR_[GS]ET_FP_MODE definitions.

(I intentionally leave the contents of the patch below so people can
comment on it)

> ---
> arch/mips/include/asm/mmu.h | 3 ++
> arch/mips/include/asm/mmu_context.h | 2 +
> arch/mips/include/asm/processor.h | 11 +++++
> arch/mips/kernel/process.c | 92 +++++++++++++++++++++++++++++++++++++
> arch/mips/kernel/traps.c | 19 ++++++++
> include/uapi/linux/prctl.h | 5 ++
> kernel/sys.c | 12 +++++
> 7 files changed, 144 insertions(+)
>
> diff --git a/arch/mips/include/asm/mmu.h b/arch/mips/include/asm/mmu.h
> index c436138..1afa1f9 100644
> --- a/arch/mips/include/asm/mmu.h
> +++ b/arch/mips/include/asm/mmu.h
> @@ -1,9 +1,12 @@
> #ifndef __ASM_MMU_H
> #define __ASM_MMU_H
>
> +#include <linux/atomic.h>
> +
> typedef struct {
> unsigned long asid[NR_CPUS];
> void *vdso;
> + atomic_t fp_mode_switching;
> } mm_context_t;
>
> #endif /* __ASM_MMU_H */
> diff --git a/arch/mips/include/asm/mmu_context.h b/arch/mips/include/asm/mmu_context.h
> index 2f82568..87f1107 100644
> --- a/arch/mips/include/asm/mmu_context.h
> +++ b/arch/mips/include/asm/mmu_context.h
> @@ -132,6 +132,8 @@ init_new_context(struct task_struct *tsk, struct mm_struct *mm)
> for_each_possible_cpu(i)
> cpu_context(i, mm) = 0;
>
> + atomic_set(&mm->context.fp_mode_switching, 0);
> +
> return 0;
> }
>
> diff --git a/arch/mips/include/asm/processor.h b/arch/mips/include/asm/processor.h
> index f1df4cb..9daa386 100644
> --- a/arch/mips/include/asm/processor.h
> +++ b/arch/mips/include/asm/processor.h
> @@ -399,4 +399,15 @@ unsigned long get_wchan(struct task_struct *p);
>
> #endif
>
> +/*
> + * Functions & macros implementing the PR_GET_FP_MODE & PR_SET_FP_MODE options
> + * to the prctl syscall.
> + */
> +extern int mips_get_process_fp_mode(struct task_struct *task);
> +extern int mips_set_process_fp_mode(struct task_struct *task,
> + unsigned int value);
> +
> +#define GET_FP_MODE(task) mips_get_process_fp_mode(task)
> +#define SET_FP_MODE(task,value) mips_set_process_fp_mode(task, value)
> +
> #endif /* _ASM_PROCESSOR_H */
> diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c
> index eb76434..b732c0c 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_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;
> +}
> diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c
> index ad3d203..d5fbfb5 100644
> --- a/arch/mips/kernel/traps.c
> +++ b/arch/mips/kernel/traps.c
> @@ -1134,10 +1134,29 @@ static int default_cu2_call(struct notifier_block *nfb, unsigned long action,
> return NOTIFY_OK;
> }
>
> +static int wait_on_fp_mode_switch(atomic_t *p)
> +{
> + /*
> + * The FP mode for this task is currently being switched. That may
> + * involve modifications to the format of this tasks FP context which
> + * make it unsafe to proceed with execution for the moment. Instead,
> + * schedule some other task.
> + */
> + schedule();
> + return 0;
> +}
> +
> static int enable_restore_fp_context(int msa)
> {
> int err, was_fpu_owner, prior_msa;
>
> + /*
> + * If an FP mode switch is currently underway, wait for it to
> + * complete before proceeding.
> + */
> + wait_on_atomic_t(&current->mm->context.fp_mode_switching,
> + wait_on_fp_mode_switch, TASK_KILLABLE);
> +
> if (!used_math()) {
> /* First time FP context user. */
> preempt_disable();
> diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
> index 89f6350..31891d9 100644
> --- a/include/uapi/linux/prctl.h
> +++ b/include/uapi/linux/prctl.h
> @@ -185,4 +185,9 @@ struct prctl_mm_map {
> #define PR_MPX_ENABLE_MANAGEMENT 43
> #define PR_MPX_DISABLE_MANAGEMENT 44
>
> +#define PR_SET_FP_MODE 45
> +#define PR_GET_FP_MODE 46
> +# define PR_FP_MODE_FR (1 << 0) /* 64b FP registers */
> +# define PR_FP_MODE_FRE (1 << 1) /* 32b compatibility */
> +
> #endif /* _LINUX_PRCTL_H */
> diff --git a/kernel/sys.c b/kernel/sys.c
> index a8c9f5a..08b16bb 100644
> --- a/kernel/sys.c
> +++ b/kernel/sys.c
> @@ -97,6 +97,12 @@
> #ifndef MPX_DISABLE_MANAGEMENT
> # define MPX_DISABLE_MANAGEMENT(a) (-EINVAL)
> #endif
> +#ifndef GET_FP_MODE
> +# define GET_FP_MODE(a) (-EINVAL)
> +#endif
> +#ifndef SET_FP_MODE
> +# define SET_FP_MODE(a,b) (-EINVAL)
> +#endif
>
> /*
> * this is where the system-wide overflow UID and GID are defined, for
> @@ -2215,6 +2221,12 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
> case PR_MPX_DISABLE_MANAGEMENT:
> error = MPX_DISABLE_MANAGEMENT(me);
> break;
> + case PR_SET_FP_MODE:
> + error = SET_FP_MODE(me, arg2);
> + break;
> + case PR_GET_FP_MODE:
> + error = GET_FP_MODE(me);
> + break;
> default:
> error = -EINVAL;
> break;
>


--
markos
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/