diff options
author | Paul Mackerras <paulus@samba.org> | 2005-10-10 08:03:41 -0400 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2005-10-10 08:03:41 -0400 |
commit | 69a80d3f69d0b2d7fae5a73c6e034d402d434d8a (patch) | |
tree | dd66f34510eae791a04c8dd77ce77a223b2f46d6 /arch/powerpc/platforms/pseries/smp.c | |
parent | ab1f9dac6eea25ee59e4c8e1cf0b7476afbbfe07 (diff) |
powerpc: move pSeries files to arch/powerpc/platforms/pseries
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/platforms/pseries/smp.c')
-rw-r--r-- | arch/powerpc/platforms/pseries/smp.c | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/pseries/smp.c b/arch/powerpc/platforms/pseries/smp.c new file mode 100644 index 000000000000..ae1bd270f308 --- /dev/null +++ b/arch/powerpc/platforms/pseries/smp.c | |||
@@ -0,0 +1,471 @@ | |||
1 | /* | ||
2 | * SMP support for pSeries machines. | ||
3 | * | ||
4 | * Dave Engebretsen, Peter Bergner, and | ||
5 | * Mike Corrigan {engebret|bergner|mikec}@us.ibm.com | ||
6 | * | ||
7 | * Plus various changes from other IBM teams... | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or | ||
10 | * modify it under the terms of the GNU General Public License | ||
11 | * as published by the Free Software Foundation; either version | ||
12 | * 2 of the License, or (at your option) any later version. | ||
13 | */ | ||
14 | |||
15 | #undef DEBUG | ||
16 | |||
17 | #include <linux/config.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/sched.h> | ||
21 | #include <linux/smp.h> | ||
22 | #include <linux/interrupt.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/spinlock.h> | ||
26 | #include <linux/cache.h> | ||
27 | #include <linux/err.h> | ||
28 | #include <linux/sysdev.h> | ||
29 | #include <linux/cpu.h> | ||
30 | |||
31 | #include <asm/ptrace.h> | ||
32 | #include <asm/atomic.h> | ||
33 | #include <asm/irq.h> | ||
34 | #include <asm/page.h> | ||
35 | #include <asm/pgtable.h> | ||
36 | #include <asm/io.h> | ||
37 | #include <asm/prom.h> | ||
38 | #include <asm/smp.h> | ||
39 | #include <asm/paca.h> | ||
40 | #include <asm/time.h> | ||
41 | #include <asm/machdep.h> | ||
42 | #include <asm/xics.h> | ||
43 | #include <asm/cputable.h> | ||
44 | #include <asm/firmware.h> | ||
45 | #include <asm/system.h> | ||
46 | #include <asm/rtas.h> | ||
47 | #include <asm/plpar_wrappers.h> | ||
48 | #include <asm/pSeries_reconfig.h> | ||
49 | #include <asm/mpic.h> | ||
50 | |||
51 | #ifdef DEBUG | ||
52 | #define DBG(fmt...) udbg_printf(fmt) | ||
53 | #else | ||
54 | #define DBG(fmt...) | ||
55 | #endif | ||
56 | |||
57 | /* | ||
58 | * The primary thread of each non-boot processor is recorded here before | ||
59 | * smp init. | ||
60 | */ | ||
61 | static cpumask_t of_spin_map; | ||
62 | |||
63 | extern void pSeries_secondary_smp_init(unsigned long); | ||
64 | |||
65 | #ifdef CONFIG_HOTPLUG_CPU | ||
66 | |||
67 | /* Get state of physical CPU. | ||
68 | * Return codes: | ||
69 | * 0 - The processor is in the RTAS stopped state | ||
70 | * 1 - stop-self is in progress | ||
71 | * 2 - The processor is not in the RTAS stopped state | ||
72 | * -1 - Hardware Error | ||
73 | * -2 - Hardware Busy, Try again later. | ||
74 | */ | ||
75 | static int query_cpu_stopped(unsigned int pcpu) | ||
76 | { | ||
77 | int cpu_status; | ||
78 | int status, qcss_tok; | ||
79 | |||
80 | qcss_tok = rtas_token("query-cpu-stopped-state"); | ||
81 | if (qcss_tok == RTAS_UNKNOWN_SERVICE) | ||
82 | return -1; | ||
83 | status = rtas_call(qcss_tok, 1, 2, &cpu_status, pcpu); | ||
84 | if (status != 0) { | ||
85 | printk(KERN_ERR | ||
86 | "RTAS query-cpu-stopped-state failed: %i\n", status); | ||
87 | return status; | ||
88 | } | ||
89 | |||
90 | return cpu_status; | ||
91 | } | ||
92 | |||
93 | int pSeries_cpu_disable(void) | ||
94 | { | ||
95 | int cpu = smp_processor_id(); | ||
96 | |||
97 | cpu_clear(cpu, cpu_online_map); | ||
98 | systemcfg->processorCount--; | ||
99 | |||
100 | /*fix boot_cpuid here*/ | ||
101 | if (cpu == boot_cpuid) | ||
102 | boot_cpuid = any_online_cpu(cpu_online_map); | ||
103 | |||
104 | /* FIXME: abstract this to not be platform specific later on */ | ||
105 | xics_migrate_irqs_away(); | ||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | void pSeries_cpu_die(unsigned int cpu) | ||
110 | { | ||
111 | int tries; | ||
112 | int cpu_status; | ||
113 | unsigned int pcpu = get_hard_smp_processor_id(cpu); | ||
114 | |||
115 | for (tries = 0; tries < 25; tries++) { | ||
116 | cpu_status = query_cpu_stopped(pcpu); | ||
117 | if (cpu_status == 0 || cpu_status == -1) | ||
118 | break; | ||
119 | msleep(200); | ||
120 | } | ||
121 | if (cpu_status != 0) { | ||
122 | printk("Querying DEAD? cpu %i (%i) shows %i\n", | ||
123 | cpu, pcpu, cpu_status); | ||
124 | } | ||
125 | |||
126 | /* Isolation and deallocation are definatly done by | ||
127 | * drslot_chrp_cpu. If they were not they would be | ||
128 | * done here. Change isolate state to Isolate and | ||
129 | * change allocation-state to Unusable. | ||
130 | */ | ||
131 | paca[cpu].cpu_start = 0; | ||
132 | } | ||
133 | |||
134 | /* | ||
135 | * Update cpu_present_map and paca(s) for a new cpu node. The wrinkle | ||
136 | * here is that a cpu device node may represent up to two logical cpus | ||
137 | * in the SMT case. We must honor the assumption in other code that | ||
138 | * the logical ids for sibling SMT threads x and y are adjacent, such | ||
139 | * that x^1 == y and y^1 == x. | ||
140 | */ | ||
141 | static int pSeries_add_processor(struct device_node *np) | ||
142 | { | ||
143 | unsigned int cpu; | ||
144 | cpumask_t candidate_map, tmp = CPU_MASK_NONE; | ||
145 | int err = -ENOSPC, len, nthreads, i; | ||
146 | u32 *intserv; | ||
147 | |||
148 | intserv = (u32 *)get_property(np, "ibm,ppc-interrupt-server#s", &len); | ||
149 | if (!intserv) | ||
150 | return 0; | ||
151 | |||
152 | nthreads = len / sizeof(u32); | ||
153 | for (i = 0; i < nthreads; i++) | ||
154 | cpu_set(i, tmp); | ||
155 | |||
156 | lock_cpu_hotplug(); | ||
157 | |||
158 | BUG_ON(!cpus_subset(cpu_present_map, cpu_possible_map)); | ||
159 | |||
160 | /* Get a bitmap of unoccupied slots. */ | ||
161 | cpus_xor(candidate_map, cpu_possible_map, cpu_present_map); | ||
162 | if (cpus_empty(candidate_map)) { | ||
163 | /* If we get here, it most likely means that NR_CPUS is | ||
164 | * less than the partition's max processors setting. | ||
165 | */ | ||
166 | printk(KERN_ERR "Cannot add cpu %s; this system configuration" | ||
167 | " supports %d logical cpus.\n", np->full_name, | ||
168 | cpus_weight(cpu_possible_map)); | ||
169 | goto out_unlock; | ||
170 | } | ||
171 | |||
172 | while (!cpus_empty(tmp)) | ||
173 | if (cpus_subset(tmp, candidate_map)) | ||
174 | /* Found a range where we can insert the new cpu(s) */ | ||
175 | break; | ||
176 | else | ||
177 | cpus_shift_left(tmp, tmp, nthreads); | ||
178 | |||
179 | if (cpus_empty(tmp)) { | ||
180 | printk(KERN_ERR "Unable to find space in cpu_present_map for" | ||
181 | " processor %s with %d thread(s)\n", np->name, | ||
182 | nthreads); | ||
183 | goto out_unlock; | ||
184 | } | ||
185 | |||
186 | for_each_cpu_mask(cpu, tmp) { | ||
187 | BUG_ON(cpu_isset(cpu, cpu_present_map)); | ||
188 | cpu_set(cpu, cpu_present_map); | ||
189 | set_hard_smp_processor_id(cpu, *intserv++); | ||
190 | } | ||
191 | err = 0; | ||
192 | out_unlock: | ||
193 | unlock_cpu_hotplug(); | ||
194 | return err; | ||
195 | } | ||
196 | |||
197 | /* | ||
198 | * Update the present map for a cpu node which is going away, and set | ||
199 | * the hard id in the paca(s) to -1 to be consistent with boot time | ||
200 | * convention for non-present cpus. | ||
201 | */ | ||
202 | static void pSeries_remove_processor(struct device_node *np) | ||
203 | { | ||
204 | unsigned int cpu; | ||
205 | int len, nthreads, i; | ||
206 | u32 *intserv; | ||
207 | |||
208 | intserv = (u32 *)get_property(np, "ibm,ppc-interrupt-server#s", &len); | ||
209 | if (!intserv) | ||
210 | return; | ||
211 | |||
212 | nthreads = len / sizeof(u32); | ||
213 | |||
214 | lock_cpu_hotplug(); | ||
215 | for (i = 0; i < nthreads; i++) { | ||
216 | for_each_present_cpu(cpu) { | ||
217 | if (get_hard_smp_processor_id(cpu) != intserv[i]) | ||
218 | continue; | ||
219 | BUG_ON(cpu_online(cpu)); | ||
220 | cpu_clear(cpu, cpu_present_map); | ||
221 | set_hard_smp_processor_id(cpu, -1); | ||
222 | break; | ||
223 | } | ||
224 | if (cpu == NR_CPUS) | ||
225 | printk(KERN_WARNING "Could not find cpu to remove " | ||
226 | "with physical id 0x%x\n", intserv[i]); | ||
227 | } | ||
228 | unlock_cpu_hotplug(); | ||
229 | } | ||
230 | |||
231 | static int pSeries_smp_notifier(struct notifier_block *nb, unsigned long action, void *node) | ||
232 | { | ||
233 | int err = NOTIFY_OK; | ||
234 | |||
235 | switch (action) { | ||
236 | case PSERIES_RECONFIG_ADD: | ||
237 | if (pSeries_add_processor(node)) | ||
238 | err = NOTIFY_BAD; | ||
239 | break; | ||
240 | case PSERIES_RECONFIG_REMOVE: | ||
241 | pSeries_remove_processor(node); | ||
242 | break; | ||
243 | default: | ||
244 | err = NOTIFY_DONE; | ||
245 | break; | ||
246 | } | ||
247 | return err; | ||
248 | } | ||
249 | |||
250 | static struct notifier_block pSeries_smp_nb = { | ||
251 | .notifier_call = pSeries_smp_notifier, | ||
252 | }; | ||
253 | |||
254 | #endif /* CONFIG_HOTPLUG_CPU */ | ||
255 | |||
256 | /** | ||
257 | * smp_startup_cpu() - start the given cpu | ||
258 | * | ||
259 | * At boot time, there is nothing to do for primary threads which were | ||
260 | * started from Open Firmware. For anything else, call RTAS with the | ||
261 | * appropriate start location. | ||
262 | * | ||
263 | * Returns: | ||
264 | * 0 - failure | ||
265 | * 1 - success | ||
266 | */ | ||
267 | static inline int __devinit smp_startup_cpu(unsigned int lcpu) | ||
268 | { | ||
269 | int status; | ||
270 | unsigned long start_here = __pa((u32)*((unsigned long *) | ||
271 | pSeries_secondary_smp_init)); | ||
272 | unsigned int pcpu; | ||
273 | int start_cpu; | ||
274 | |||
275 | if (cpu_isset(lcpu, of_spin_map)) | ||
276 | /* Already started by OF and sitting in spin loop */ | ||
277 | return 1; | ||
278 | |||
279 | pcpu = get_hard_smp_processor_id(lcpu); | ||
280 | |||
281 | /* Fixup atomic count: it exited inside IRQ handler. */ | ||
282 | paca[lcpu].__current->thread_info->preempt_count = 0; | ||
283 | |||
284 | /* | ||
285 | * If the RTAS start-cpu token does not exist then presume the | ||
286 | * cpu is already spinning. | ||
287 | */ | ||
288 | start_cpu = rtas_token("start-cpu"); | ||
289 | if (start_cpu == RTAS_UNKNOWN_SERVICE) | ||
290 | return 1; | ||
291 | |||
292 | status = rtas_call(start_cpu, 3, 1, NULL, pcpu, start_here, lcpu); | ||
293 | if (status != 0) { | ||
294 | printk(KERN_ERR "start-cpu failed: %i\n", status); | ||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | return 1; | ||
299 | } | ||
300 | |||
301 | #ifdef CONFIG_XICS | ||
302 | static inline void smp_xics_do_message(int cpu, int msg) | ||
303 | { | ||
304 | set_bit(msg, &xics_ipi_message[cpu].value); | ||
305 | mb(); | ||
306 | xics_cause_IPI(cpu); | ||
307 | } | ||
308 | |||
309 | static void smp_xics_message_pass(int target, int msg) | ||
310 | { | ||
311 | unsigned int i; | ||
312 | |||
313 | if (target < NR_CPUS) { | ||
314 | smp_xics_do_message(target, msg); | ||
315 | } else { | ||
316 | for_each_online_cpu(i) { | ||
317 | if (target == MSG_ALL_BUT_SELF | ||
318 | && i == smp_processor_id()) | ||
319 | continue; | ||
320 | smp_xics_do_message(i, msg); | ||
321 | } | ||
322 | } | ||
323 | } | ||
324 | |||
325 | static int __init smp_xics_probe(void) | ||
326 | { | ||
327 | xics_request_IPIs(); | ||
328 | |||
329 | return cpus_weight(cpu_possible_map); | ||
330 | } | ||
331 | |||
332 | static void __devinit smp_xics_setup_cpu(int cpu) | ||
333 | { | ||
334 | if (cpu != boot_cpuid) | ||
335 | xics_setup_cpu(); | ||
336 | |||
337 | if (firmware_has_feature(FW_FEATURE_SPLPAR)) | ||
338 | vpa_init(cpu); | ||
339 | |||
340 | cpu_clear(cpu, of_spin_map); | ||
341 | |||
342 | } | ||
343 | #endif /* CONFIG_XICS */ | ||
344 | |||
345 | static DEFINE_SPINLOCK(timebase_lock); | ||
346 | static unsigned long timebase = 0; | ||
347 | |||
348 | static void __devinit pSeries_give_timebase(void) | ||
349 | { | ||
350 | spin_lock(&timebase_lock); | ||
351 | rtas_call(rtas_token("freeze-time-base"), 0, 1, NULL); | ||
352 | timebase = get_tb(); | ||
353 | spin_unlock(&timebase_lock); | ||
354 | |||
355 | while (timebase) | ||
356 | barrier(); | ||
357 | rtas_call(rtas_token("thaw-time-base"), 0, 1, NULL); | ||
358 | } | ||
359 | |||
360 | static void __devinit pSeries_take_timebase(void) | ||
361 | { | ||
362 | while (!timebase) | ||
363 | barrier(); | ||
364 | spin_lock(&timebase_lock); | ||
365 | set_tb(timebase >> 32, timebase & 0xffffffff); | ||
366 | timebase = 0; | ||
367 | spin_unlock(&timebase_lock); | ||
368 | } | ||
369 | |||
370 | static void __devinit smp_pSeries_kick_cpu(int nr) | ||
371 | { | ||
372 | BUG_ON(nr < 0 || nr >= NR_CPUS); | ||
373 | |||
374 | if (!smp_startup_cpu(nr)) | ||
375 | return; | ||
376 | |||
377 | /* | ||
378 | * The processor is currently spinning, waiting for the | ||
379 | * cpu_start field to become non-zero After we set cpu_start, | ||
380 | * the processor will continue on to secondary_start | ||
381 | */ | ||
382 | paca[nr].cpu_start = 1; | ||
383 | } | ||
384 | |||
385 | static int smp_pSeries_cpu_bootable(unsigned int nr) | ||
386 | { | ||
387 | /* Special case - we inhibit secondary thread startup | ||
388 | * during boot if the user requests it. Odd-numbered | ||
389 | * cpus are assumed to be secondary threads. | ||
390 | */ | ||
391 | if (system_state < SYSTEM_RUNNING && | ||
392 | cpu_has_feature(CPU_FTR_SMT) && | ||
393 | !smt_enabled_at_boot && nr % 2 != 0) | ||
394 | return 0; | ||
395 | |||
396 | return 1; | ||
397 | } | ||
398 | #ifdef CONFIG_MPIC | ||
399 | static struct smp_ops_t pSeries_mpic_smp_ops = { | ||
400 | .message_pass = smp_mpic_message_pass, | ||
401 | .probe = smp_mpic_probe, | ||
402 | .kick_cpu = smp_pSeries_kick_cpu, | ||
403 | .setup_cpu = smp_mpic_setup_cpu, | ||
404 | }; | ||
405 | #endif | ||
406 | #ifdef CONFIG_XICS | ||
407 | static struct smp_ops_t pSeries_xics_smp_ops = { | ||
408 | .message_pass = smp_xics_message_pass, | ||
409 | .probe = smp_xics_probe, | ||
410 | .kick_cpu = smp_pSeries_kick_cpu, | ||
411 | .setup_cpu = smp_xics_setup_cpu, | ||
412 | .cpu_bootable = smp_pSeries_cpu_bootable, | ||
413 | }; | ||
414 | #endif | ||
415 | |||
416 | /* This is called very early */ | ||
417 | void __init smp_init_pSeries(void) | ||
418 | { | ||
419 | int i; | ||
420 | |||
421 | DBG(" -> smp_init_pSeries()\n"); | ||
422 | |||
423 | switch (ppc64_interrupt_controller) { | ||
424 | #ifdef CONFIG_MPIC | ||
425 | case IC_OPEN_PIC: | ||
426 | smp_ops = &pSeries_mpic_smp_ops; | ||
427 | break; | ||
428 | #endif | ||
429 | #ifdef CONFIG_XICS | ||
430 | case IC_PPC_XIC: | ||
431 | smp_ops = &pSeries_xics_smp_ops; | ||
432 | break; | ||
433 | #endif | ||
434 | default: | ||
435 | panic("Invalid interrupt controller"); | ||
436 | } | ||
437 | |||
438 | #ifdef CONFIG_HOTPLUG_CPU | ||
439 | smp_ops->cpu_disable = pSeries_cpu_disable; | ||
440 | smp_ops->cpu_die = pSeries_cpu_die; | ||
441 | |||
442 | /* Processors can be added/removed only on LPAR */ | ||
443 | if (systemcfg->platform == PLATFORM_PSERIES_LPAR) | ||
444 | pSeries_reconfig_notifier_register(&pSeries_smp_nb); | ||
445 | #endif | ||
446 | |||
447 | /* Mark threads which are still spinning in hold loops. */ | ||
448 | if (cpu_has_feature(CPU_FTR_SMT)) { | ||
449 | for_each_present_cpu(i) { | ||
450 | if (i % 2 == 0) | ||
451 | /* | ||
452 | * Even-numbered logical cpus correspond to | ||
453 | * primary threads. | ||
454 | */ | ||
455 | cpu_set(i, of_spin_map); | ||
456 | } | ||
457 | } else { | ||
458 | of_spin_map = cpu_present_map; | ||
459 | } | ||
460 | |||
461 | cpu_clear(boot_cpuid, of_spin_map); | ||
462 | |||
463 | /* Non-lpar has additional take/give timebase */ | ||
464 | if (rtas_token("freeze-time-base") != RTAS_UNKNOWN_SERVICE) { | ||
465 | smp_ops->give_timebase = pSeries_give_timebase; | ||
466 | smp_ops->take_timebase = pSeries_take_timebase; | ||
467 | } | ||
468 | |||
469 | DBG(" <- smp_init_pSeries()\n"); | ||
470 | } | ||
471 | |||