Commit 91381b9c authored by Cyril Bur's avatar Cyril Bur Committed by Michael Ellerman

powerpc: Force reload for recheckpoint during tm {fp, vec, vsx} unavailable exception

Lazy save and restore of FP/Altivec means that a userspace process can
be sent to userspace with FP or Altivec disabled and loaded only as
required (by way of an FP/Altivec unavailable exception). Transactional
Memory complicates this situation as a transaction could be started
without FP/Altivec being loaded up. This causes the hardware to
checkpoint incorrect registers. Handling FP/Altivec unavailable
exceptions while a thread is transactional requires a reclaim and
recheckpoint to ensure the CPU has correct state for both sets of
registers.

tm_reclaim() has optimisations to not always save the FP/Altivec
registers to the checkpointed save area. This was originally done
because the caller might have information that the checkpointed
registers aren't valid due to lazy save and restore. We've also been a
little vague as to how tm_reclaim() leaves the FP/Altivec state since it
doesn't necessarily always save it to the thread struct. This has lead
to an (incorrect) assumption that it leaves the checkpointed state on
the CPU.

tm_recheckpoint() has similar optimisations in reverse. It may not
always reload the checkpointed FP/Altivec registers from the thread
struct before the trecheckpoint. It is therefore quite unclear where it
expects to get the state from. This didn't help with the assumption
made about tm_reclaim().

This patch is a minimal fix for ease of backporting. A more correct fix
which removes the msr parameter to tm_reclaim() and tm_recheckpoint()
altogether has been upstreamed to apply on top of this patch.

Fixes: dc310669 ("powerpc: tm: Always use fp_state and vr_state to
store live registers")
Signed-off-by: default avatarCyril Bur <cyrilbur@gmail.com>
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
parent a7771176
...@@ -874,6 +874,8 @@ static void tm_reclaim_thread(struct thread_struct *thr, ...@@ -874,6 +874,8 @@ static void tm_reclaim_thread(struct thread_struct *thr,
if (!MSR_TM_SUSPENDED(mfmsr())) if (!MSR_TM_SUSPENDED(mfmsr()))
return; return;
giveup_all(container_of(thr, struct task_struct, thread));
/* /*
* If we are in a transaction and FP is off then we can't have * If we are in a transaction and FP is off then we can't have
* used FP inside that transaction. Hence the checkpointed * used FP inside that transaction. Hence the checkpointed
...@@ -893,8 +895,6 @@ static void tm_reclaim_thread(struct thread_struct *thr, ...@@ -893,8 +895,6 @@ static void tm_reclaim_thread(struct thread_struct *thr,
memcpy(&thr->ckvr_state, &thr->vr_state, memcpy(&thr->ckvr_state, &thr->vr_state,
sizeof(struct thread_vr_state)); sizeof(struct thread_vr_state));
giveup_all(container_of(thr, struct task_struct, thread));
tm_reclaim(thr, thr->ckpt_regs.msr, cause); tm_reclaim(thr, thr->ckpt_regs.msr, cause);
} }
......
...@@ -1663,6 +1663,12 @@ void facility_unavailable_exception(struct pt_regs *regs) ...@@ -1663,6 +1663,12 @@ void facility_unavailable_exception(struct pt_regs *regs)
void fp_unavailable_tm(struct pt_regs *regs) void fp_unavailable_tm(struct pt_regs *regs)
{ {
/*
* Save the MSR now because tm_reclaim_current() is likely to
* change it
*/
unsigned long orig_msr = regs->msr;
/* Note: This does not handle any kind of FP laziness. */ /* Note: This does not handle any kind of FP laziness. */
TM_DEBUG("FP Unavailable trap whilst transactional at 0x%lx, MSR=%lx\n", TM_DEBUG("FP Unavailable trap whilst transactional at 0x%lx, MSR=%lx\n",
...@@ -1687,10 +1693,10 @@ void fp_unavailable_tm(struct pt_regs *regs) ...@@ -1687,10 +1693,10 @@ void fp_unavailable_tm(struct pt_regs *regs)
* If VMX is in use, the VRs now hold checkpointed values, * If VMX is in use, the VRs now hold checkpointed values,
* so we don't want to load the VRs from the thread_struct. * so we don't want to load the VRs from the thread_struct.
*/ */
tm_recheckpoint(&current->thread, MSR_FP); tm_recheckpoint(&current->thread, orig_msr | MSR_FP);
/* If VMX is in use, get the transactional values back */ /* If VMX is in use, get the transactional values back */
if (regs->msr & MSR_VEC) { if (orig_msr & MSR_VEC) {
msr_check_and_set(MSR_VEC); msr_check_and_set(MSR_VEC);
load_vr_state(&current->thread.vr_state); load_vr_state(&current->thread.vr_state);
/* At this point all the VSX state is loaded, so enable it */ /* At this point all the VSX state is loaded, so enable it */
...@@ -1700,6 +1706,12 @@ void fp_unavailable_tm(struct pt_regs *regs) ...@@ -1700,6 +1706,12 @@ void fp_unavailable_tm(struct pt_regs *regs)
void altivec_unavailable_tm(struct pt_regs *regs) void altivec_unavailable_tm(struct pt_regs *regs)
{ {
/*
* Save the MSR now because tm_reclaim_current() is likely to
* change it
*/
unsigned long orig_msr = regs->msr;
/* See the comments in fp_unavailable_tm(). This function operates /* See the comments in fp_unavailable_tm(). This function operates
* the same way. * the same way.
*/ */
...@@ -1709,10 +1721,10 @@ void altivec_unavailable_tm(struct pt_regs *regs) ...@@ -1709,10 +1721,10 @@ void altivec_unavailable_tm(struct pt_regs *regs)
regs->nip, regs->msr); regs->nip, regs->msr);
tm_reclaim_current(TM_CAUSE_FAC_UNAV); tm_reclaim_current(TM_CAUSE_FAC_UNAV);
current->thread.load_vec = 1; current->thread.load_vec = 1;
tm_recheckpoint(&current->thread, MSR_VEC); tm_recheckpoint(&current->thread, orig_msr | MSR_VEC);
current->thread.used_vr = 1; current->thread.used_vr = 1;
if (regs->msr & MSR_FP) { if (orig_msr & MSR_FP) {
msr_check_and_set(MSR_FP); msr_check_and_set(MSR_FP);
load_fp_state(&current->thread.fp_state); load_fp_state(&current->thread.fp_state);
regs->msr |= MSR_VSX; regs->msr |= MSR_VSX;
...@@ -1751,7 +1763,7 @@ void vsx_unavailable_tm(struct pt_regs *regs) ...@@ -1751,7 +1763,7 @@ void vsx_unavailable_tm(struct pt_regs *regs)
/* This loads & recheckpoints FP and VRs; but we have /* This loads & recheckpoints FP and VRs; but we have
* to be sure not to overwrite previously-valid state. * to be sure not to overwrite previously-valid state.
*/ */
tm_recheckpoint(&current->thread, regs->msr & ~orig_msr); tm_recheckpoint(&current->thread, orig_msr | MSR_FP | MSR_VEC);
msr_check_and_set(orig_msr & (MSR_FP | MSR_VEC)); msr_check_and_set(orig_msr & (MSR_FP | MSR_VEC));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment