1336 lines · 32.7 KB · C
RawDownload
/* *  Derived from "arch/i386/kernel/process.c" *    Copyright (C) 1995  Linus Torvalds * *  Updated and modified by Cort Dougan (cort@cs.nmt.edu) and *  Paul Mackerras (paulus@cs.anu.edu.au) * *  PowerPC version *    Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org) * *  This program is free software; you can redistribute it and/or *  modify it under the terms of the GNU General Public License *  as published by the Free Software Foundation; either version *  2 of the License, or (at your option) any later version. */ #include <linux/errno.h>#include <linux/sched.h>#include <linux/kernel.h>#include <linux/mm.h>#include <linux/smp.h>#include <linux/stddef.h>#include <linux/unistd.h>#include <linux/ptrace.h>#include <linux/slab.h>#include <linux/user.h>#include <linux/elf.h>#include <linux/init.h>#include <linux/prctl.h>#include <linux/init_task.h>#include <linux/export.h>#include <linux/kallsyms.h>#include <linux/mqueue.h>#include <linux/hardirq.h>#include <linux/utsname.h>#include <linux/ftrace.h>#include <linux/kernel_stat.h>#include <linux/personality.h>#include <linux/random.h>#include <linux/hw_breakpoint.h> #include <asm/pgtable.h>#include <asm/uaccess.h>#include <asm/system.h>#include <asm/io.h>#include <asm/processor.h>#include <asm/mmu.h>#include <asm/prom.h>#include <asm/machdep.h>#include <asm/time.h>#include <asm/syscalls.h>#ifdef CONFIG_PPC64#include <asm/firmware.h>#endif#include <linux/kprobes.h>#include <linux/kdebug.h> extern unsigned long _get_SP(void); #ifndef CONFIG_SMPstruct task_struct *last_task_used_math = NULL;struct task_struct *last_task_used_altivec = NULL;struct task_struct *last_task_used_vsx = NULL;struct task_struct *last_task_used_spe = NULL;#endif /* * Make sure the floating-point register state in the * the thread_struct is up to date for task tsk. */void flush_fp_to_thread(struct task_struct *tsk){	if (tsk->thread.regs) {		/*		 * We need to disable preemption here because if we didn't,		 * another process could get scheduled after the regs->msr		 * test but before we have finished saving the FP registers		 * to the thread_struct.  That process could take over the		 * FPU, and then when we get scheduled again we would store		 * bogus values for the remaining FP registers.		 */		preempt_disable();		if (tsk->thread.regs->msr & MSR_FP) {#ifdef CONFIG_SMP			/*			 * This should only ever be called for current or			 * for a stopped child process.  Since we save away			 * the FP register state on context switch on SMP,			 * there is something wrong if a stopped child appears			 * to still have its FP state in the CPU registers.			 */			BUG_ON(tsk != current);#endif			giveup_fpu(tsk);		}		preempt_enable();	}}EXPORT_SYMBOL_GPL(flush_fp_to_thread); void enable_kernel_fp(void){	WARN_ON(preemptible()); #ifdef CONFIG_SMP	if (current->thread.regs && (current->thread.regs->msr & MSR_FP))		giveup_fpu(current);	else		giveup_fpu(NULL);	/* just enables FP for kernel */#else	giveup_fpu(last_task_used_math);#endif /* CONFIG_SMP */}EXPORT_SYMBOL(enable_kernel_fp); #ifdef CONFIG_ALTIVECvoid enable_kernel_altivec(void){	WARN_ON(preemptible()); #ifdef CONFIG_SMP	if (current->thread.regs && (current->thread.regs->msr & MSR_VEC))		giveup_altivec(current);	else		giveup_altivec(NULL);	/* just enable AltiVec for kernel - force */#else	giveup_altivec(last_task_used_altivec);#endif /* CONFIG_SMP */}EXPORT_SYMBOL(enable_kernel_altivec); /* * Make sure the VMX/Altivec register state in the * the thread_struct is up to date for task tsk. */void flush_altivec_to_thread(struct task_struct *tsk){	if (tsk->thread.regs) {		preempt_disable();		if (tsk->thread.regs->msr & MSR_VEC) {#ifdef CONFIG_SMP			BUG_ON(tsk != current);#endif			giveup_altivec(tsk);		}		preempt_enable();	}}EXPORT_SYMBOL_GPL(flush_altivec_to_thread);#endif /* CONFIG_ALTIVEC */ #ifdef CONFIG_VSX#if 0/* not currently used, but some crazy RAID module might want to later */void enable_kernel_vsx(void){	WARN_ON(preemptible()); #ifdef CONFIG_SMP	if (current->thread.regs && (current->thread.regs->msr & MSR_VSX))		giveup_vsx(current);	else		giveup_vsx(NULL);	/* just enable vsx for kernel - force */#else	giveup_vsx(last_task_used_vsx);#endif /* CONFIG_SMP */}EXPORT_SYMBOL(enable_kernel_vsx);#endif void giveup_vsx(struct task_struct *tsk){	giveup_fpu(tsk);	giveup_altivec(tsk);	__giveup_vsx(tsk);} void flush_vsx_to_thread(struct task_struct *tsk){	if (tsk->thread.regs) {		preempt_disable();		if (tsk->thread.regs->msr & MSR_VSX) {#ifdef CONFIG_SMP			BUG_ON(tsk != current);#endif			giveup_vsx(tsk);		}		preempt_enable();	}}EXPORT_SYMBOL_GPL(flush_vsx_to_thread);#endif /* CONFIG_VSX */ #ifdef CONFIG_SPE void enable_kernel_spe(void){	WARN_ON(preemptible()); #ifdef CONFIG_SMP	if (current->thread.regs && (current->thread.regs->msr & MSR_SPE))		giveup_spe(current);	else		giveup_spe(NULL);	/* just enable SPE for kernel - force */#else	giveup_spe(last_task_used_spe);#endif /* __SMP __ */}EXPORT_SYMBOL(enable_kernel_spe); void flush_spe_to_thread(struct task_struct *tsk){	if (tsk->thread.regs) {		preempt_disable();		if (tsk->thread.regs->msr & MSR_SPE) {#ifdef CONFIG_SMP			BUG_ON(tsk != current);#endif			tsk->thread.spefscr = mfspr(SPRN_SPEFSCR);			giveup_spe(tsk);		}		preempt_enable();	}}#endif /* CONFIG_SPE */ #ifndef CONFIG_SMP/* * If we are doing lazy switching of CPU state (FP, altivec or SPE), * and the current task has some state, discard it. */void discard_lazy_cpu_state(void){	preempt_disable();	if (last_task_used_math == current)		last_task_used_math = NULL;#ifdef CONFIG_ALTIVEC	if (last_task_used_altivec == current)		last_task_used_altivec = NULL;#endif /* CONFIG_ALTIVEC */#ifdef CONFIG_VSX	if (last_task_used_vsx == current)		last_task_used_vsx = NULL;#endif /* CONFIG_VSX */#ifdef CONFIG_SPE	if (last_task_used_spe == current)		last_task_used_spe = NULL;#endif	preempt_enable();}#endif /* CONFIG_SMP */ #ifdef CONFIG_PPC_ADV_DEBUG_REGSvoid do_send_trap(struct pt_regs *regs, unsigned long address,		  unsigned long error_code, int signal_code, int breakpt){	siginfo_t info; 	if (notify_die(DIE_DABR_MATCH, "dabr_match", regs, error_code,			11, SIGSEGV) == NOTIFY_STOP)		return; 	/* Deliver the signal to userspace */	info.si_signo = SIGTRAP;	info.si_errno = breakpt;	/* breakpoint or watchpoint id */	info.si_code = signal_code;	info.si_addr = (void __user *)address;	force_sig_info(SIGTRAP, &info, current);}#else	/* !CONFIG_PPC_ADV_DEBUG_REGS */void do_dabr(struct pt_regs *regs, unsigned long address,		    unsigned long error_code){	siginfo_t info; 	if (notify_die(DIE_DABR_MATCH, "dabr_match", regs, error_code,			11, SIGSEGV) == NOTIFY_STOP)		return; 	if (debugger_dabr_match(regs))		return; 	/* Clear the DABR */	set_dabr(0); 	/* Deliver the signal to userspace */	info.si_signo = SIGTRAP;	info.si_errno = 0;	info.si_code = TRAP_HWBKPT;	info.si_addr = (void __user *)address;	force_sig_info(SIGTRAP, &info, current);}#endif	/* CONFIG_PPC_ADV_DEBUG_REGS */ static DEFINE_PER_CPU(unsigned long, current_dabr); #ifdef CONFIG_PPC_ADV_DEBUG_REGS/* * Set the debug registers back to their default "safe" values. */static void set_debug_reg_defaults(struct thread_struct *thread){	thread->iac1 = thread->iac2 = 0;#if CONFIG_PPC_ADV_DEBUG_IACS > 2	thread->iac3 = thread->iac4 = 0;#endif	thread->dac1 = thread->dac2 = 0;#if CONFIG_PPC_ADV_DEBUG_DVCS > 0	thread->dvc1 = thread->dvc2 = 0;#endif	thread->dbcr0 = 0;#ifdef CONFIG_BOOKE	/*	 * Force User/Supervisor bits to b11 (user-only MSR[PR]=1)	 */	thread->dbcr1 = DBCR1_IAC1US | DBCR1_IAC2US |	\			DBCR1_IAC3US | DBCR1_IAC4US;	/*	 * Force Data Address Compare User/Supervisor bits to be User-only	 * (0b11 MSR[PR]=1) and set all other bits in DBCR2 register to be 0.	 */	thread->dbcr2 = DBCR2_DAC1US | DBCR2_DAC2US;#else	thread->dbcr1 = 0;#endif} static void prime_debug_regs(struct thread_struct *thread){	mtspr(SPRN_IAC1, thread->iac1);	mtspr(SPRN_IAC2, thread->iac2);#if CONFIG_PPC_ADV_DEBUG_IACS > 2	mtspr(SPRN_IAC3, thread->iac3);	mtspr(SPRN_IAC4, thread->iac4);#endif	mtspr(SPRN_DAC1, thread->dac1);	mtspr(SPRN_DAC2, thread->dac2);#if CONFIG_PPC_ADV_DEBUG_DVCS > 0	mtspr(SPRN_DVC1, thread->dvc1);	mtspr(SPRN_DVC2, thread->dvc2);#endif	mtspr(SPRN_DBCR0, thread->dbcr0);	mtspr(SPRN_DBCR1, thread->dbcr1);#ifdef CONFIG_BOOKE	mtspr(SPRN_DBCR2, thread->dbcr2);#endif}/* * Unless neither the old or new thread are making use of the * debug registers, set the debug registers from the values * stored in the new thread. */static void switch_booke_debug_regs(struct thread_struct *new_thread){	if ((current->thread.dbcr0 & DBCR0_IDM)		|| (new_thread->dbcr0 & DBCR0_IDM))			prime_debug_regs(new_thread);}#else	/* !CONFIG_PPC_ADV_DEBUG_REGS */#ifndef CONFIG_HAVE_HW_BREAKPOINTstatic void set_debug_reg_defaults(struct thread_struct *thread){	if (thread->dabr) {		thread->dabr = 0;		set_dabr(0);	}}#endif /* !CONFIG_HAVE_HW_BREAKPOINT */#endif	/* CONFIG_PPC_ADV_DEBUG_REGS */ int set_dabr(unsigned long dabr){	__get_cpu_var(current_dabr) = dabr; 	if (ppc_md.set_dabr)		return ppc_md.set_dabr(dabr); 	/* XXX should we have a CPU_FTR_HAS_DABR ? */#ifdef CONFIG_PPC_ADV_DEBUG_REGS	mtspr(SPRN_DAC1, dabr);#ifdef CONFIG_PPC_47x	isync();#endif#elif defined(CONFIG_PPC_BOOK3S)	mtspr(SPRN_DABR, dabr);#endif  	return 0;} #ifdef CONFIG_PPC64DEFINE_PER_CPU(struct cpu_usage, cpu_usage_array);#endif struct task_struct *__switch_to(struct task_struct *prev,	struct task_struct *new){	struct thread_struct *new_thread, *old_thread;	unsigned long flags;	struct task_struct *last;#ifdef CONFIG_PPC_BOOK3S_64	struct ppc64_tlb_batch *batch;#endif #ifdef CONFIG_SMP	/* avoid complexity of lazy save/restore of fpu	 * by just saving it every time we switch out if	 * this task used the fpu during the last quantum.	 *	 * If it tries to use the fpu again, it'll trap and	 * reload its fp regs.  So we don't have to do a restore	 * every switch, just a save.	 *  -- Cort	 */	if (prev->thread.regs && (prev->thread.regs->msr & MSR_FP))		giveup_fpu(prev);#ifdef CONFIG_ALTIVEC	/*	 * If the previous thread used altivec in the last quantum	 * (thus changing altivec regs) then save them.	 * We used to check the VRSAVE register but not all apps	 * set it, so we don't rely on it now (and in fact we need	 * to save & restore VSCR even if VRSAVE == 0).  -- paulus	 *	 * On SMP we always save/restore altivec regs just to avoid the	 * complexity of changing processors.	 *  -- Cort	 */	if (prev->thread.regs && (prev->thread.regs->msr & MSR_VEC))		giveup_altivec(prev);#endif /* CONFIG_ALTIVEC */#ifdef CONFIG_VSX	if (prev->thread.regs && (prev->thread.regs->msr & MSR_VSX))		/* VMX and FPU registers are already save here */		__giveup_vsx(prev);#endif /* CONFIG_VSX */#ifdef CONFIG_SPE	/*	 * If the previous thread used spe in the last quantum	 * (thus changing spe regs) then save them.	 *	 * On SMP we always save/restore spe regs just to avoid the	 * complexity of changing processors.	 */	if ((prev->thread.regs && (prev->thread.regs->msr & MSR_SPE)))		giveup_spe(prev);#endif /* CONFIG_SPE */ #else  /* CONFIG_SMP */#ifdef CONFIG_ALTIVEC	/* Avoid the trap.  On smp this this never happens since	 * we don't set last_task_used_altivec -- Cort	 */	if (new->thread.regs && last_task_used_altivec == new)		new->thread.regs->msr |= MSR_VEC;#endif /* CONFIG_ALTIVEC */#ifdef CONFIG_VSX	if (new->thread.regs && last_task_used_vsx == new)		new->thread.regs->msr |= MSR_VSX;#endif /* CONFIG_VSX */#ifdef CONFIG_SPE	/* Avoid the trap.  On smp this this never happens since	 * we don't set last_task_used_spe	 */	if (new->thread.regs && last_task_used_spe == new)		new->thread.regs->msr |= MSR_SPE;#endif /* CONFIG_SPE */ #endif /* CONFIG_SMP */ #ifdef CONFIG_PPC_ADV_DEBUG_REGS	switch_booke_debug_regs(&new->thread);#else/* * For PPC_BOOK3S_64, we use the hw-breakpoint interfaces that would * schedule DABR */#ifndef CONFIG_HAVE_HW_BREAKPOINT	if (unlikely(__get_cpu_var(current_dabr) != new->thread.dabr))		set_dabr(new->thread.dabr);#endif /* CONFIG_HAVE_HW_BREAKPOINT */#endif  	new_thread = &new->thread;	old_thread = &current->thread; #ifdef CONFIG_PPC64	/*	 * Collect processor utilization data per process	 */	if (firmware_has_feature(FW_FEATURE_SPLPAR)) {		struct cpu_usage *cu = &__get_cpu_var(cpu_usage_array);		long unsigned start_tb, current_tb;		start_tb = old_thread->start_tb;		cu->current_tb = current_tb = mfspr(SPRN_PURR);		old_thread->accum_tb += (current_tb - start_tb);		new_thread->start_tb = current_tb;	}

Lines 1–500 of 1336.