diff options
Diffstat (limited to 'arch/powerpc/kernel/process.c')
| -rw-r--r-- | arch/powerpc/kernel/process.c | 181 | 
1 files changed, 106 insertions, 75 deletions
diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c index 9ee2623e0f67..9e7c10fe205f 100644 --- a/arch/powerpc/kernel/process.c +++ b/arch/powerpc/kernel/process.c @@ -59,6 +59,7 @@  #include <asm/exec.h>  #include <asm/livepatch.h>  #include <asm/cpu_has_feature.h> +#include <asm/asm-prototypes.h>  #include <linux/kprobes.h>  #include <linux/kdebug.h> @@ -88,7 +89,13 @@ static void check_if_tm_restore_required(struct task_struct *tsk)  		set_thread_flag(TIF_RESTORE_TM);  	}  } + +static inline bool msr_tm_active(unsigned long msr) +{ +	return MSR_TM_ACTIVE(msr); +}  #else +static inline bool msr_tm_active(unsigned long msr) { return false; }  static inline void check_if_tm_restore_required(struct task_struct *tsk) { }  #endif /* CONFIG_PPC_TRANSACTIONAL_MEM */ @@ -104,7 +111,7 @@ static int __init enable_strict_msr_control(char *str)  }  early_param("ppc_strict_facility_enable", enable_strict_msr_control); -void msr_check_and_set(unsigned long bits) +unsigned long msr_check_and_set(unsigned long bits)  {  	unsigned long oldmsr = mfmsr();  	unsigned long newmsr; @@ -118,6 +125,8 @@ void msr_check_and_set(unsigned long bits)  	if (oldmsr != newmsr)  		mtmsr_isync(newmsr); + +	return newmsr;  }  void __msr_check_and_clear(unsigned long bits) @@ -196,19 +205,30 @@ EXPORT_SYMBOL_GPL(flush_fp_to_thread);  void enable_kernel_fp(void)  { +	unsigned long cpumsr; +  	WARN_ON(preemptible()); -	msr_check_and_set(MSR_FP); +	cpumsr = msr_check_and_set(MSR_FP);  	if (current->thread.regs && (current->thread.regs->msr & MSR_FP)) {  		check_if_tm_restore_required(current); +		/* +		 * If a thread has already been reclaimed then the +		 * checkpointed registers are on the CPU but have definitely +		 * been saved by the reclaim code. Don't need to and *cannot* +		 * giveup as this would save  to the 'live' structure not the +		 * checkpointed structure. +		 */ +		if(!msr_tm_active(cpumsr) && msr_tm_active(current->thread.regs->msr)) +			return;  		__giveup_fpu(current);  	}  }  EXPORT_SYMBOL(enable_kernel_fp);  static int restore_fp(struct task_struct *tsk) { -	if (tsk->thread.load_fp) { +	if (tsk->thread.load_fp || msr_tm_active(tsk->thread.regs->msr)) {  		load_fp_state(¤t->thread.fp_state);  		current->thread.load_fp++;  		return 1; @@ -248,12 +268,23 @@ EXPORT_SYMBOL(giveup_altivec);  void enable_kernel_altivec(void)  { +	unsigned long cpumsr; +  	WARN_ON(preemptible()); -	msr_check_and_set(MSR_VEC); +	cpumsr = msr_check_and_set(MSR_VEC);  	if (current->thread.regs && (current->thread.regs->msr & MSR_VEC)) {  		check_if_tm_restore_required(current); +		/* +		 * If a thread has already been reclaimed then the +		 * checkpointed registers are on the CPU but have definitely +		 * been saved by the reclaim code. Don't need to and *cannot* +		 * giveup as this would save  to the 'live' structure not the +		 * checkpointed structure. +		 */ +		if(!msr_tm_active(cpumsr) && msr_tm_active(current->thread.regs->msr)) +			return;  		__giveup_altivec(current);  	}  } @@ -278,7 +309,8 @@ EXPORT_SYMBOL_GPL(flush_altivec_to_thread);  static int restore_altivec(struct task_struct *tsk)  { -	if (cpu_has_feature(CPU_FTR_ALTIVEC) && tsk->thread.load_vec) { +	if (cpu_has_feature(CPU_FTR_ALTIVEC) && +		(tsk->thread.load_vec || msr_tm_active(tsk->thread.regs->msr))) {  		load_vr_state(&tsk->thread.vr_state);  		tsk->thread.used_vr = 1;  		tsk->thread.load_vec++; @@ -321,12 +353,23 @@ static void save_vsx(struct task_struct *tsk)  void enable_kernel_vsx(void)  { +	unsigned long cpumsr; +  	WARN_ON(preemptible()); -	msr_check_and_set(MSR_FP|MSR_VEC|MSR_VSX); +	cpumsr = msr_check_and_set(MSR_FP|MSR_VEC|MSR_VSX);  	if (current->thread.regs && (current->thread.regs->msr & MSR_VSX)) {  		check_if_tm_restore_required(current); +		/* +		 * If a thread has already been reclaimed then the +		 * checkpointed registers are on the CPU but have definitely +		 * been saved by the reclaim code. Don't need to and *cannot* +		 * giveup as this would save  to the 'live' structure not the +		 * checkpointed structure. +		 */ +		if(!msr_tm_active(cpumsr) && msr_tm_active(current->thread.regs->msr)) +			return;  		if (current->thread.regs->msr & MSR_FP)  			__giveup_fpu(current);  		if (current->thread.regs->msr & MSR_VEC) @@ -438,6 +481,7 @@ void giveup_all(struct task_struct *tsk)  		return;  	msr_check_and_set(msr_all_available); +	check_if_tm_restore_required(tsk);  #ifdef CONFIG_PPC_FPU  	if (usermsr & MSR_FP) @@ -464,7 +508,8 @@ void restore_math(struct pt_regs *regs)  {  	unsigned long msr; -	if (!current->thread.load_fp && !loadvec(current->thread)) +	if (!msr_tm_active(regs->msr) && +		!current->thread.load_fp && !loadvec(current->thread))  		return;  	msr = regs->msr; @@ -767,29 +812,15 @@ static inline bool hw_brk_match(struct arch_hw_breakpoint *a,  }  #ifdef CONFIG_PPC_TRANSACTIONAL_MEM + +static inline bool tm_enabled(struct task_struct *tsk) +{ +	return tsk && tsk->thread.regs && (tsk->thread.regs->msr & MSR_TM); +} +  static void tm_reclaim_thread(struct thread_struct *thr,  			      struct thread_info *ti, uint8_t cause)  { -	unsigned long msr_diff = 0; - -	/* -	 * If FP/VSX registers have been already saved to the -	 * thread_struct, move them to the transact_fp array. -	 * We clear the TIF_RESTORE_TM bit since after the reclaim -	 * the thread will no longer be transactional. -	 */ -	if (test_ti_thread_flag(ti, TIF_RESTORE_TM)) { -		msr_diff = thr->ckpt_regs.msr & ~thr->regs->msr; -		if (msr_diff & MSR_FP) -			memcpy(&thr->transact_fp, &thr->fp_state, -			       sizeof(struct thread_fp_state)); -		if (msr_diff & MSR_VEC) -			memcpy(&thr->transact_vr, &thr->vr_state, -			       sizeof(struct thread_vr_state)); -		clear_ti_thread_flag(ti, TIF_RESTORE_TM); -		msr_diff &= MSR_FP | MSR_VEC | MSR_VSX | MSR_FE0 | MSR_FE1; -	} -  	/*  	 * Use the current MSR TM suspended bit to track if we have  	 * checkpointed state outstanding. @@ -808,15 +839,9 @@ static void tm_reclaim_thread(struct thread_struct *thr,  	if (!MSR_TM_SUSPENDED(mfmsr()))  		return; -	tm_reclaim(thr, thr->regs->msr, cause); +	giveup_all(container_of(thr, struct task_struct, thread)); -	/* Having done the reclaim, we now have the checkpointed -	 * FP/VSX values in the registers.  These might be valid -	 * even if we have previously called enable_kernel_fp() or -	 * flush_fp_to_thread(), so update thr->regs->msr to -	 * indicate their current validity. -	 */ -	thr->regs->msr |= msr_diff; +	tm_reclaim(thr, thr->ckpt_regs.msr, cause);  }  void tm_reclaim_current(uint8_t cause) @@ -832,8 +857,8 @@ static inline void tm_reclaim_task(struct task_struct *tsk)  	 *  	 * In switching we need to maintain a 2nd register state as  	 * oldtask->thread.ckpt_regs.  We tm_reclaim(oldproc); this saves the -	 * checkpointed (tbegin) state in ckpt_regs and saves the transactional -	 * (current) FPRs into oldtask->thread.transact_fpr[]. +	 * checkpointed (tbegin) state in ckpt_regs, ckfp_state and +	 * ckvr_state  	 *  	 * We also context switch (save) TFHAR/TEXASR/TFIAR in here.  	 */ @@ -845,14 +870,6 @@ static inline void tm_reclaim_task(struct task_struct *tsk)  	if (!MSR_TM_ACTIVE(thr->regs->msr))  		goto out_and_saveregs; -	/* Stash the original thread MSR, as giveup_fpu et al will -	 * modify it.  We hold onto it to see whether the task used -	 * FP & vector regs.  If the TIF_RESTORE_TM flag is set, -	 * ckpt_regs.msr is already set. -	 */ -	if (!test_ti_thread_flag(task_thread_info(tsk), TIF_RESTORE_TM)) -		thr->ckpt_regs.msr = thr->regs->msr; -  	TM_DEBUG("--- tm_reclaim on pid %d (NIP=%lx, "  		 "ccr=%lx, msr=%lx, trap=%lx)\n",  		 tsk->pid, thr->regs->nip, @@ -881,6 +898,9 @@ void tm_recheckpoint(struct thread_struct *thread,  {  	unsigned long flags; +	if (!(thread->regs->msr & MSR_TM)) +		return; +  	/* We really can't be interrupted here as the TEXASR registers can't  	 * change and later in the trecheckpoint code, we have a userspace R1.  	 * So let's hard disable over this region. @@ -910,10 +930,10 @@ static inline void tm_recheckpoint_new_task(struct task_struct *new)  	 * If the task was using FP, we non-lazily reload both the original and  	 * the speculative FP register states.  This is because the kernel  	 * doesn't see if/when a TM rollback occurs, so if we take an FP -	 * unavoidable later, we are unable to determine which set of FP regs +	 * unavailable later, we are unable to determine which set of FP regs  	 * need to be restored.  	 */ -	if (!new->thread.regs) +	if (!tm_enabled(new))  		return;  	if (!MSR_TM_ACTIVE(new->thread.regs->msr)){ @@ -926,35 +946,35 @@ static inline void tm_recheckpoint_new_task(struct task_struct *new)  		 "(new->msr 0x%lx, new->origmsr 0x%lx)\n",  		 new->pid, new->thread.regs->msr, msr); -	/* This loads the checkpointed FP/VEC state, if used */  	tm_recheckpoint(&new->thread, msr); -	/* This loads the speculative FP/VEC state, if used */ -	if (msr & MSR_FP) { -		do_load_up_transact_fpu(&new->thread); -		new->thread.regs->msr |= -			(MSR_FP | new->thread.fpexc_mode); -	} -#ifdef CONFIG_ALTIVEC -	if (msr & MSR_VEC) { -		do_load_up_transact_altivec(&new->thread); -		new->thread.regs->msr |= MSR_VEC; -	} -#endif -	/* We may as well turn on VSX too since all the state is restored now */ -	if (msr & MSR_VSX) -		new->thread.regs->msr |= MSR_VSX; +	/* +	 * The checkpointed state has been restored but the live state has +	 * not, ensure all the math functionality is turned off to trigger +	 * restore_math() to reload. +	 */ +	new->thread.regs->msr &= ~(MSR_FP | MSR_VEC | MSR_VSX);  	TM_DEBUG("*** tm_recheckpoint of pid %d complete "  		 "(kernel msr 0x%lx)\n",  		 new->pid, mfmsr());  } -static inline void __switch_to_tm(struct task_struct *prev) +static inline void __switch_to_tm(struct task_struct *prev, +		struct task_struct *new)  {  	if (cpu_has_feature(CPU_FTR_TM)) { -		tm_enable(); -		tm_reclaim_task(prev); +		if (tm_enabled(prev) || tm_enabled(new)) +			tm_enable(); + +		if (tm_enabled(prev)) { +			prev->thread.load_tm++; +			tm_reclaim_task(prev); +			if (!MSR_TM_ACTIVE(prev->thread.regs->msr) && prev->thread.load_tm == 0) +				prev->thread.regs->msr &= ~MSR_TM; +		} + +		tm_recheckpoint_new_task(new);  	}  } @@ -976,6 +996,12 @@ void restore_tm_state(struct pt_regs *regs)  {  	unsigned long msr_diff; +	/* +	 * This is the only moment we should clear TIF_RESTORE_TM as +	 * it is here that ckpt_regs.msr and pt_regs.msr become the same +	 * again, anything else could lead to an incorrect ckpt_msr being +	 * saved and therefore incorrect signal contexts. +	 */  	clear_thread_flag(TIF_RESTORE_TM);  	if (!MSR_TM_ACTIVE(regs->msr))  		return; @@ -983,6 +1009,13 @@ void restore_tm_state(struct pt_regs *regs)  	msr_diff = current->thread.ckpt_regs.msr & ~regs->msr;  	msr_diff &= MSR_FP | MSR_VEC | MSR_VSX; +	/* Ensure that restore_math() will restore */ +	if (msr_diff & MSR_FP) +		current->thread.load_fp = 1; +#ifdef CONFIG_ALIVEC +	if (cpu_has_feature(CPU_FTR_ALTIVEC) && msr_diff & MSR_VEC) +		current->thread.load_vec = 1; +#endif  	restore_math(regs);  	regs->msr |= msr_diff; @@ -990,7 +1023,7 @@ void restore_tm_state(struct pt_regs *regs)  #else  #define tm_recheckpoint_new_task(new) -#define __switch_to_tm(prev) +#define __switch_to_tm(prev, new)  #endif /* CONFIG_PPC_TRANSACTIONAL_MEM */  static inline void save_sprs(struct thread_struct *t) @@ -1131,11 +1164,11 @@ struct task_struct *__switch_to(struct task_struct *prev,  	 */  	save_sprs(&prev->thread); -	__switch_to_tm(prev); -  	/* Save FPU, Altivec, VSX and SPE state */  	giveup_all(prev); +	__switch_to_tm(prev, new); +  	/*  	 * We can't take a PMU exception inside _switch() since there is a  	 * window where the kernel stack SLB and the kernel stack are out @@ -1143,8 +1176,6 @@ struct task_struct *__switch_to(struct task_struct *prev,  	 */  	hard_irq_disable(); -	tm_recheckpoint_new_task(new); -  	/*  	 * Call restore_sprs() before calling _switch(). If we move it after  	 * _switch() then we miss out on calling it for new tasks. The reason @@ -1379,9 +1410,11 @@ int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)  	 * transitions the CPU out of TM mode.  Hence we need to call  	 * tm_recheckpoint_new_task() (on the same task) to restore the  	 * checkpointed state back and the TM mode. +	 * +	 * Can't pass dst because it isn't ready. Doesn't matter, passing +	 * dst is only important for __switch_to()  	 */ -	__switch_to_tm(src); -	tm_recheckpoint_new_task(src); +	__switch_to_tm(src, src);  	*dst = *src; @@ -1623,8 +1656,6 @@ void start_thread(struct pt_regs *regs, unsigned long start, unsigned long sp)  	current->thread.used_spe = 0;  #endif /* CONFIG_SPE */  #ifdef CONFIG_PPC_TRANSACTIONAL_MEM -	if (cpu_has_feature(CPU_FTR_TM)) -		regs->msr |= MSR_TM;  	current->thread.tm_tfhar = 0;  	current->thread.tm_texasr = 0;  	current->thread.tm_tfiar = 0;  |