aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Neuling <mikey@neuling.org>2010-05-13 15:40:11 -0400
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2010-05-21 03:31:11 -0400
commit1fc711f7ffb01089efc58042cfdbac8573d1b59a (patch)
tree7c057a32c140e8d617a9cff34ca5d2227e9acdb8
parentd504bed676caad29a3dba3d3727298c560628f5c (diff)
powerpc/kexec: Fix race in kexec shutdown
In kexec_prepare_cpus, the primary CPU IPIs the secondary CPUs to kexec_smp_down(). kexec_smp_down() calls kexec_smp_wait() which sets the hw_cpu_id() to -1. The primary does this while leaving IRQs on which means the primary can take a timer interrupt which can lead to the IPIing one of the secondary CPUs (say, for a scheduler re-balance) but since the secondary CPU now has a hw_cpu_id = -1, we IPI CPU -1... Kaboom! We are hitting this case regularly on POWER7 machines. There is also a second race, where the primary will tear down the MMU mappings before knowing the secondaries have entered real mode. Also, the secondaries are clearing out any pending IPIs before guaranteeing that no more will be received. This changes kexec_prepare_cpus() so that we turn off IRQs in the primary CPU much earlier. It adds a paca flag to say that the secondaries have entered the kexec_smp_down() IPI and turned off IRQs, rather than overloading hw_cpu_id with -1. This new paca flag is again used to in indicate when the secondaries has entered real mode. It also ensures that all CPUs have their IRQs off before we clear out any pending IPI requests (in kexec_cpu_down()) to ensure there are no trailing IPIs left unacknowledged. Signed-off-by: Michael Neuling <mikey@neuling.org> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
-rw-r--r--arch/powerpc/include/asm/kexec.h4
-rw-r--r--arch/powerpc/include/asm/paca.h1
-rw-r--r--arch/powerpc/kernel/asm-offsets.c1
-rw-r--r--arch/powerpc/kernel/machine_kexec_64.c48
-rw-r--r--arch/powerpc/kernel/misc_64.S8
-rw-r--r--arch/powerpc/kernel/paca.c2
6 files changed, 47 insertions, 17 deletions
diff --git a/arch/powerpc/include/asm/kexec.h b/arch/powerpc/include/asm/kexec.h
index 7e06b43720d3..a6ca6da1430b 100644
--- a/arch/powerpc/include/asm/kexec.h
+++ b/arch/powerpc/include/asm/kexec.h
@@ -31,6 +31,10 @@
31#define KEXEC_ARCH KEXEC_ARCH_PPC 31#define KEXEC_ARCH KEXEC_ARCH_PPC
32#endif 32#endif
33 33
34#define KEXEC_STATE_NONE 0
35#define KEXEC_STATE_IRQS_OFF 1
36#define KEXEC_STATE_REAL_MODE 2
37
34#ifndef __ASSEMBLY__ 38#ifndef __ASSEMBLY__
35#include <linux/cpumask.h> 39#include <linux/cpumask.h>
36#include <asm/reg.h> 40#include <asm/reg.h>
diff --git a/arch/powerpc/include/asm/paca.h b/arch/powerpc/include/asm/paca.h
index a011603d4079..971dfa4815f0 100644
--- a/arch/powerpc/include/asm/paca.h
+++ b/arch/powerpc/include/asm/paca.h
@@ -82,6 +82,7 @@ struct paca_struct {
82 s16 hw_cpu_id; /* Physical processor number */ 82 s16 hw_cpu_id; /* Physical processor number */
83 u8 cpu_start; /* At startup, processor spins until */ 83 u8 cpu_start; /* At startup, processor spins until */
84 /* this becomes non-zero. */ 84 /* this becomes non-zero. */
85 u8 kexec_state; /* set when kexec down has irqs off */
85#ifdef CONFIG_PPC_STD_MMU_64 86#ifdef CONFIG_PPC_STD_MMU_64
86 struct slb_shadow *slb_shadow_ptr; 87 struct slb_shadow *slb_shadow_ptr;
87 88
diff --git a/arch/powerpc/kernel/asm-offsets.c b/arch/powerpc/kernel/asm-offsets.c
index 0271b58ec31e..1b784ff92d9d 100644
--- a/arch/powerpc/kernel/asm-offsets.c
+++ b/arch/powerpc/kernel/asm-offsets.c
@@ -184,6 +184,7 @@ int main(void)
184#endif /* CONFIG_PPC_STD_MMU_64 */ 184#endif /* CONFIG_PPC_STD_MMU_64 */
185 DEFINE(PACAEMERGSP, offsetof(struct paca_struct, emergency_sp)); 185 DEFINE(PACAEMERGSP, offsetof(struct paca_struct, emergency_sp));
186 DEFINE(PACAHWCPUID, offsetof(struct paca_struct, hw_cpu_id)); 186 DEFINE(PACAHWCPUID, offsetof(struct paca_struct, hw_cpu_id));
187 DEFINE(PACAKEXECSTATE, offsetof(struct paca_struct, kexec_state));
187 DEFINE(PACA_STARTPURR, offsetof(struct paca_struct, startpurr)); 188 DEFINE(PACA_STARTPURR, offsetof(struct paca_struct, startpurr));
188 DEFINE(PACA_STARTSPURR, offsetof(struct paca_struct, startspurr)); 189 DEFINE(PACA_STARTSPURR, offsetof(struct paca_struct, startspurr));
189 DEFINE(PACA_USER_TIME, offsetof(struct paca_struct, user_time)); 190 DEFINE(PACA_USER_TIME, offsetof(struct paca_struct, user_time));
diff --git a/arch/powerpc/kernel/machine_kexec_64.c b/arch/powerpc/kernel/machine_kexec_64.c
index 040bd1de8d99..26f9900f773c 100644
--- a/arch/powerpc/kernel/machine_kexec_64.c
+++ b/arch/powerpc/kernel/machine_kexec_64.c
@@ -155,33 +155,38 @@ void kexec_copy_flush(struct kimage *image)
155 155
156#ifdef CONFIG_SMP 156#ifdef CONFIG_SMP
157 157
158/* FIXME: we should schedule this function to be called on all cpus based 158static int kexec_all_irq_disabled = 0;
159 * on calling the interrupts, but we would like to call it off irq level 159
160 * so that the interrupt controller is clean.
161 */
162static void kexec_smp_down(void *arg) 160static void kexec_smp_down(void *arg)
163{ 161{
162 local_irq_disable();
163 mb(); /* make sure our irqs are disabled before we say they are */
164 get_paca()->kexec_state = KEXEC_STATE_IRQS_OFF;
165 while(kexec_all_irq_disabled == 0)
166 cpu_relax();
167 mb(); /* make sure all irqs are disabled before this */
168 /*
169 * Now every CPU has IRQs off, we can clear out any pending
170 * IPIs and be sure that no more will come in after this.
171 */
164 if (ppc_md.kexec_cpu_down) 172 if (ppc_md.kexec_cpu_down)
165 ppc_md.kexec_cpu_down(0, 1); 173 ppc_md.kexec_cpu_down(0, 1);
166 174
167 local_irq_disable();
168 kexec_smp_wait(); 175 kexec_smp_wait();
169 /* NOTREACHED */ 176 /* NOTREACHED */
170} 177}
171 178
172static void kexec_prepare_cpus(void) 179static void kexec_prepare_cpus_wait(int wait_state)
173{ 180{
174 int my_cpu, i, notified=-1; 181 int my_cpu, i, notified=-1;
175 182
176 smp_call_function(kexec_smp_down, NULL, /* wait */0);
177 my_cpu = get_cpu(); 183 my_cpu = get_cpu();
178 184 /* Make sure each CPU has atleast made it to the state we need */
179 /* check the others cpus are now down (via paca hw cpu id == -1) */
180 for (i=0; i < NR_CPUS; i++) { 185 for (i=0; i < NR_CPUS; i++) {
181 if (i == my_cpu) 186 if (i == my_cpu)
182 continue; 187 continue;
183 188
184 while (paca[i].hw_cpu_id != -1) { 189 while (paca[i].kexec_state < wait_state) {
185 barrier(); 190 barrier();
186 if (!cpu_possible(i)) { 191 if (!cpu_possible(i)) {
187 printk("kexec: cpu %d hw_cpu_id %d is not" 192 printk("kexec: cpu %d hw_cpu_id %d is not"
@@ -201,20 +206,35 @@ static void kexec_prepare_cpus(void)
201 } 206 }
202 if (i != notified) { 207 if (i != notified) {
203 printk( "kexec: waiting for cpu %d (physical" 208 printk( "kexec: waiting for cpu %d (physical"
204 " %d) to go down\n", 209 " %d) to enter %i state\n",
205 i, paca[i].hw_cpu_id); 210 i, paca[i].hw_cpu_id, wait_state);
206 notified = i; 211 notified = i;
207 } 212 }
208 } 213 }
209 } 214 }
215 mb();
216}
217
218static void kexec_prepare_cpus(void)
219{
220
221 smp_call_function(kexec_smp_down, NULL, /* wait */0);
222 local_irq_disable();
223 mb(); /* make sure IRQs are disabled before we say they are */
224 get_paca()->kexec_state = KEXEC_STATE_IRQS_OFF;
225
226 kexec_prepare_cpus_wait(KEXEC_STATE_IRQS_OFF);
227 /* we are sure every CPU has IRQs off at this point */
228 kexec_all_irq_disabled = 1;
210 229
211 /* after we tell the others to go down */ 230 /* after we tell the others to go down */
212 if (ppc_md.kexec_cpu_down) 231 if (ppc_md.kexec_cpu_down)
213 ppc_md.kexec_cpu_down(0, 0); 232 ppc_md.kexec_cpu_down(0, 0);
214 233
215 put_cpu(); 234 /* Before removing MMU mapings make sure all CPUs have entered real mode */
235 kexec_prepare_cpus_wait(KEXEC_STATE_REAL_MODE);
216 236
217 local_irq_disable(); 237 put_cpu();
218} 238}
219 239
220#else /* ! SMP */ 240#else /* ! SMP */
diff --git a/arch/powerpc/kernel/misc_64.S b/arch/powerpc/kernel/misc_64.S
index a5cf9c1356a6..a2b18dffa03e 100644
--- a/arch/powerpc/kernel/misc_64.S
+++ b/arch/powerpc/kernel/misc_64.S
@@ -24,6 +24,7 @@
24#include <asm/asm-offsets.h> 24#include <asm/asm-offsets.h>
25#include <asm/cputable.h> 25#include <asm/cputable.h>
26#include <asm/thread_info.h> 26#include <asm/thread_info.h>
27#include <asm/kexec.h>
27 28
28 .text 29 .text
29 30
@@ -471,6 +472,10 @@ _GLOBAL(kexec_wait)
4711: mflr r5 4721: mflr r5
472 addi r5,r5,kexec_flag-1b 473 addi r5,r5,kexec_flag-1b
473 474
475 li r4,KEXEC_STATE_REAL_MODE
476 stb r4,PACAKEXECSTATE(r13)
477 SYNC
478
47499: HMT_LOW 47999: HMT_LOW
475#ifdef CONFIG_KEXEC /* use no memory without kexec */ 480#ifdef CONFIG_KEXEC /* use no memory without kexec */
476 lwz r4,0(r5) 481 lwz r4,0(r5)
@@ -494,14 +499,11 @@ kexec_flag:
494 * note: this is a terminal routine, it does not save lr 499 * note: this is a terminal routine, it does not save lr
495 * 500 *
496 * get phys id from paca 501 * get phys id from paca
497 * set paca id to -1 to say we got here
498 * switch to real mode 502 * switch to real mode
499 * join other cpus in kexec_wait(phys_id) 503 * join other cpus in kexec_wait(phys_id)
500 */ 504 */
501_GLOBAL(kexec_smp_wait) 505_GLOBAL(kexec_smp_wait)
502 lhz r3,PACAHWCPUID(r13) 506 lhz r3,PACAHWCPUID(r13)
503 li r4,-1
504 sth r4,PACAHWCPUID(r13) /* let others know we left */
505 bl real_mode 507 bl real_mode
506 b .kexec_wait 508 b .kexec_wait
507 509
diff --git a/arch/powerpc/kernel/paca.c b/arch/powerpc/kernel/paca.c
index 0c40c6f476fe..f88acf0218db 100644
--- a/arch/powerpc/kernel/paca.c
+++ b/arch/powerpc/kernel/paca.c
@@ -18,6 +18,7 @@
18#include <asm/pgtable.h> 18#include <asm/pgtable.h>
19#include <asm/iseries/lpar_map.h> 19#include <asm/iseries/lpar_map.h>
20#include <asm/iseries/hv_types.h> 20#include <asm/iseries/hv_types.h>
21#include <asm/kexec.h>
21 22
22/* This symbol is provided by the linker - let it fill in the paca 23/* This symbol is provided by the linker - let it fill in the paca
23 * field correctly */ 24 * field correctly */
@@ -97,6 +98,7 @@ void __init initialise_paca(struct paca_struct *new_paca, int cpu)
97 new_paca->kernelbase = (unsigned long) _stext; 98 new_paca->kernelbase = (unsigned long) _stext;
98 new_paca->kernel_msr = MSR_KERNEL; 99 new_paca->kernel_msr = MSR_KERNEL;
99 new_paca->hw_cpu_id = 0xffff; 100 new_paca->hw_cpu_id = 0xffff;
101 new_paca->kexec_state = KEXEC_STATE_NONE;
100 new_paca->__current = &init_task; 102 new_paca->__current = &init_task;
101#ifdef CONFIG_PPC_STD_MMU_64 103#ifdef CONFIG_PPC_STD_MMU_64
102 new_paca->slb_shadow_ptr = &slb_shadow[cpu]; 104 new_paca->slb_shadow_ptr = &slb_shadow[cpu];