[RFC PATCH] arm64/sve: ABI change: Zero SVE regs on syscall entry
Dave Martin
Dave.Martin at arm.com
Tue Oct 24 03:45:29 PDT 2017
On Mon, Oct 23, 2017 at 06:43:23PM +0100, Catalin Marinas wrote:
> Hi Dave,
>
> On Mon, Oct 23, 2017 at 12:37:44PM +0100, Dave P Martin wrote:
> > diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
> > index 6718780..68917de 100644
> > --- a/arch/arm64/kernel/entry.S
> > +++ b/arch/arm64/kernel/entry.S
> > @@ -850,6 +850,25 @@ el0_svc:
> > adrp stbl, sys_call_table // load syscall table pointer
> > mov wscno, w8 // syscall number in w8
> > mov wsc_nr, #__NR_syscalls
> > +
> > +#ifdef CONFIG_ARM64_SVE
> > +alternative_if_not ARM64_SVE
> > + b 1f
> > +alternative_else
> > + ldr x10, [tsk, #TSK_TI_FLAGS]
> > +alternative_endif
> > + bic x10, x10, #_TIF_SVE // discard SVE state on syscall
>
> Could we have:
>
> tbz x10, #TIF_SVE, 1f
This depends on how likely this branch is to be mispredicted, which I
don't have a good feel for.
I thought the store was likely to be free or very inexpensive: we read/
write the thread flags elsewhere in the svc path anyway, so I presumed
this would mostly move cost around rather than adding it.
I don't have a strong opinion, though...
>
> > + str x10, [tsk, #TSK_TI_FLAGS]
> > +
> > + tbz x10, #TIF_FOREIGN_FPSTATE, 2f
> > + ASM_BUG()
> > +2:
>
> If we've never hit this problem before, I think we can avoid the
> TIF_FOREIGN_FPSTATE here. Just double-check the return path that we
> always invoke do_notify_resume().
Sure, that was me debugging: I'll remove that.
I did hit this, but only because I'd written tbnz by mistake...
>
> > + mrs x9, cpacr_el1
> > + bic x9, x9, #CPACR_EL1_ZEN_EL0EN // disable SVE for el0
> > + msr cpacr_el1, x9 // synchronised by eret to el0
>
> I haven't checked the other patches but do we context switch cpacr_el1?
> If not, we may return to user with this enabled. Alternatively, can we
> disable it on the return path?
Currently, cpacr_el1 is only written via ret_to_user when
TIF_FOREIGN_FPSTATE is set (i.e., if there is pending context switch
work to do anyway).
We could write this unconditionally in ret_to_user, but
a) I was trying to keep the changes minimal for the first spin of the
patch, and
b) By the time we reach ret_to_user, we no longer know whether we are
in a syscall: an in_syscall() check would be needed.
>
> > +1:
> > +#endif
> > +
> > el0_svc_naked: // compat entry point
> > stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number
> > enable_dbg_and_irq
>
> I think you could move the TIF_SVE checks above in el0_svc_naked and
> reuse x16 to avoid loading TSK_TI_FLAGS twice. For compat tasks TIF_SVE
> would be 0 anyway (as long as we can skip the whole block as per my
> suggested tbz).
Could do. This code is irrelevant to the compat case, so I was trying
to keep it separate.
Alternatively, I could
el0_svc:
ldr x16, [tsk, #TSK_TI_FLAGS]
//...
b 3f
el0_evc_naked:
ldr x16, [tsk, #TSK_TI_FLAGS]
3:
which does the load only once, but means hoisting the load before
enable_dbg_and_irq in the el0_svc_naked case. It's also a bit ugly.
Thoughts?
OTOH, doing the load twice looks cheap: it should be hot in the cache.
>
> > diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
> > index ac56a96..9a3c561 100644
> > --- a/arch/arm64/kernel/process.c
> > +++ b/arch/arm64/kernel/process.c
> > @@ -247,22 +247,22 @@ void arch_release_task_struct(struct task_struct *tsk)
> > fpsimd_release_task(tsk);
> > }
> >
> > +/*
> > + * src and dst may temporarily have aliased sve_state after task_struct
> > + * is copied. We cannot fix this properly here, because src may have
> > + * live SVE state and dst's thread_info may not exist yet, so tweaking
> > + * either src's or dst's TIF_SVE is not safe.
> > + *
> > + * The unaliasing is done in copy_thread() instead. This works because
> > + * dst is not schedulable or traceable until both of these functions
> > + * have been called.
> > + */
> > int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
> > {
> > - /*
> > - * For SVE, dst and src must not end up with aliases of the same
> > - * sve_state. Because we are definitely in a syscall here, SVE state
> > - * can be discarded unconditionally without violating the user ABI.
> > - * dst's sve_state pointer can then be zapped with no ill effects.
> > - *
> > - * src can safely retain its sve_state memory for later use.
> > - */
> > if (current->mm)
> > - fpsimd_preserve_current_state_discard_sve();
> > + fpsimd_preserve_current_state();
> > *dst = *src;
> >
> > - dst->thread.sve_state = NULL;
> > -
> > return 0;
> > }
> >
> > @@ -275,6 +275,13 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start,
> >
> > memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));
> >
> > + /*
> > + * Unalias p->thread.sve_state (if any) from the parent task
> > + * and disable discard SVE state for p:
> > + */
> > + clear_tsk_thread_flag(p, TIF_SVE);
> > + p->thread.sve_state = NULL;
> > +
> > if (likely(!(p->flags & PF_KTHREAD))) {
> > *childregs = *current_pt_regs();
> > childregs->regs[0] = 0;
>
> This looks fine.
OK, thanks
---Dave
More information about the linux-arm-kernel
mailing list