From 8117ce76c28ef0cab8545b518fa0543f6d1437e6 Mon Sep 17 00:00:00 2001 From: Paolo Galtieri Date: Tue, 29 Nov 2005 14:26:47 -0800 Subject: [PATCH] ppc: fix floating point register corruption I recently discovered a bug on PPC which causes the floating point registers to get corrupted when CONFIG_PREEMPT=y. The problem occurred while running a multi threaded Java application that does floating point. The problem could be reproduced in anywhere from 2 to 6 hours. With the patch I have included below it ran for over a week without failure. Signed-off-by: Paolo Galtieri Cc: Kumar Gala Cc: Matt Porter Cc: Tom Rini Cc: Benjamin Herrenschmidt Signed-off-by: Andrew Morton Signed-off-by: Paul Mackerras --- arch/ppc/kernel/process.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/ppc/kernel/process.c b/arch/ppc/kernel/process.c index cb1c7b92f8c601..25cbdc8d2941a9 100644 --- a/arch/ppc/kernel/process.c +++ b/arch/ppc/kernel/process.c @@ -417,6 +417,7 @@ void show_regs(struct pt_regs * regs) void exit_thread(void) { + preempt_disable(); if (last_task_used_math == current) last_task_used_math = NULL; if (last_task_used_altivec == current) @@ -425,10 +426,12 @@ void exit_thread(void) if (last_task_used_spe == current) last_task_used_spe = NULL; #endif + preempt_enable(); } void flush_thread(void) { + preempt_disable(); if (last_task_used_math == current) last_task_used_math = NULL; if (last_task_used_altivec == current) @@ -437,6 +440,7 @@ void flush_thread(void) if (last_task_used_spe == current) last_task_used_spe = NULL; #endif + preempt_enable(); } void @@ -535,6 +539,7 @@ void start_thread(struct pt_regs *regs, unsigned long nip, unsigned long sp) regs->nip = nip; regs->gpr[1] = sp; regs->msr = MSR_USER; + preempt_disable(); if (last_task_used_math == current) last_task_used_math = NULL; if (last_task_used_altivec == current) @@ -543,6 +548,7 @@ void start_thread(struct pt_regs *regs, unsigned long nip, unsigned long sp) if (last_task_used_spe == current) last_task_used_spe = NULL; #endif + preempt_enable(); memset(current->thread.fpr, 0, sizeof(current->thread.fpr)); current->thread.fpscr.val = 0; #ifdef CONFIG_ALTIVEC -- cgit 1.2.3-korg From 48abec07cf8063184d397560a6a5f27eaf9caddf Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Wed, 30 Nov 2005 13:20:54 +1100 Subject: powerpc: Fix bug causing FP registers corruption on UP + preempt This fixes a bug noticed by Paolo Galtieri and fixed for ARCH=ppc in the previous commit (ppc: fix floating point register corruption). This fixes the arch/powerpc code by adding preempt_disable/enable, and also cleans it up a bit by pulling out the code that discards any lazily-switched CPU register state into a new function, rather than having that code repeated in three places. Signed-off-by: Paul Mackerras --- arch/powerpc/kernel/process.c | 62 +++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c index a5a7542a8ff348..105d5609ff572d 100644 --- a/arch/powerpc/kernel/process.c +++ b/arch/powerpc/kernel/process.c @@ -201,6 +201,28 @@ int dump_spe(struct pt_regs *regs, elf_vrregset_t *evrregs) } #endif /* CONFIG_SPE */ +/* + * If we are doing lazy switching of CPU state (FP, altivec or SPE), + * and the current task has some state, discard it. + */ +static inline void discard_lazy_cpu_state(void) +{ +#ifndef CONFIG_SMP + 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_SPE + if (last_task_used_spe == current) + last_task_used_spe = NULL; +#endif + preempt_enable(); +#endif /* CONFIG_SMP */ +} + int set_dabr(unsigned long dabr) { if (ppc_md.set_dabr) @@ -434,19 +456,7 @@ void show_regs(struct pt_regs * regs) void exit_thread(void) { kprobe_flush_task(current); - -#ifndef CONFIG_SMP - 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_SPE - if (last_task_used_spe == current) - last_task_used_spe = NULL; -#endif -#endif /* CONFIG_SMP */ + discard_lazy_cpu_state(); } void flush_thread(void) @@ -458,18 +468,7 @@ void flush_thread(void) t->flags ^= (_TIF_ABI_PENDING | _TIF_32BIT); #endif -#ifndef CONFIG_SMP - 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_SPE - if (last_task_used_spe == current) - last_task_used_spe = NULL; -#endif -#endif /* CONFIG_SMP */ + discard_lazy_cpu_state(); #ifdef CONFIG_PPC64 /* for now */ if (current->thread.dabr) { @@ -635,18 +634,7 @@ void start_thread(struct pt_regs *regs, unsigned long start, unsigned long sp) } #endif -#ifndef CONFIG_SMP - 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 -#ifdef CONFIG_SPE - if (last_task_used_spe == current) - last_task_used_spe = NULL; -#endif -#endif /* CONFIG_SMP */ + discard_lazy_cpu_state(); memset(current->thread.fpr, 0, sizeof(current->thread.fpr)); current->thread.fpscr.val = 0; #ifdef CONFIG_ALTIVEC -- cgit 1.2.3-korg From ed1189b7e8cd8144f0b232c220aed4ee26d89463 Mon Sep 17 00:00:00 2001 From: Olaf Hering Date: Tue, 29 Nov 2005 14:04:05 +0100 Subject: [PATCH] powerpc: prevent stack corruption in call_prom_ret Use the correct pointer to clear the memory of the return values, to prevent stack corruption in the callers stackframe. Signed-off-by: Olaf Hering Signed-off-by: Paul Mackerras --- arch/powerpc/kernel/prom_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/prom_init.c b/arch/powerpc/kernel/prom_init.c index 4ce0105c308ea0..bcdc209dca8525 100644 --- a/arch/powerpc/kernel/prom_init.c +++ b/arch/powerpc/kernel/prom_init.c @@ -265,7 +265,7 @@ static int __init call_prom_ret(const char *service, int nargs, int nret, va_end(list); for (i = 0; i < nret; i++) - rets[nargs+i] = 0; + args.args[nargs+i] = 0; if (enter_prom(&args, RELOC(prom_entry)) < 0) return PROM_ERROR; -- cgit 1.2.3-korg