]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
0332c2d4 ME |
2 | /* |
3 | * pseries CPU Hotplug infrastructure. | |
4 | * | |
413f7c40 ME |
5 | * Split out from arch/powerpc/platforms/pseries/setup.c |
6 | * arch/powerpc/kernel/rtas.c, and arch/powerpc/platforms/pseries/smp.c | |
0332c2d4 ME |
7 | * |
8 | * Peter Bergner, IBM March 2001. | |
9 | * Copyright (C) 2001 IBM. | |
413f7c40 ME |
10 | * Dave Engebretsen, Peter Bergner, and |
11 | * Mike Corrigan {engebret|bergner|mikec}@us.ibm.com | |
12 | * Plus various changes from other IBM teams... | |
0332c2d4 ME |
13 | * |
14 | * Copyright (C) 2006 Michael Ellerman, IBM Corporation | |
0332c2d4 ME |
15 | */ |
16 | ||
e666ae0b NF |
17 | #define pr_fmt(fmt) "pseries-hotplug-cpu: " fmt |
18 | ||
0332c2d4 | 19 | #include <linux/kernel.h> |
0b05ac6e | 20 | #include <linux/interrupt.h> |
0332c2d4 | 21 | #include <linux/delay.h> |
62fe91bb | 22 | #include <linux/sched.h> /* for idle_task_exit */ |
ef8bd77f | 23 | #include <linux/sched/hotplug.h> |
0332c2d4 | 24 | #include <linux/cpu.h> |
1cf3d8b3 | 25 | #include <linux/of.h> |
ac713800 | 26 | #include <linux/slab.h> |
0332c2d4 ME |
27 | #include <asm/prom.h> |
28 | #include <asm/rtas.h> | |
29 | #include <asm/firmware.h> | |
30 | #include <asm/machdep.h> | |
31 | #include <asm/vdso_datapage.h> | |
0b05ac6e | 32 | #include <asm/xics.h> |
eac1e731 | 33 | #include <asm/xive.h> |
212bebb4 | 34 | #include <asm/plpar_wrappers.h> |
1d9a0907 | 35 | #include <asm/topology.h> |
212bebb4 | 36 | |
183deeea | 37 | #include "pseries.h" |
3aa565f5 | 38 | #include "offline_states.h" |
0332c2d4 ME |
39 | |
40 | /* This version can't take the spinlock, because it never returns */ | |
41dd03a9 | 41 | static int rtas_stop_self_token = RTAS_UNKNOWN_SERVICE; |
0332c2d4 | 42 | |
3aa565f5 GS |
43 | static DEFINE_PER_CPU(enum cpu_state_vals, preferred_offline_state) = |
44 | CPU_STATE_OFFLINE; | |
45 | static DEFINE_PER_CPU(enum cpu_state_vals, current_state) = CPU_STATE_OFFLINE; | |
46 | ||
47 | static enum cpu_state_vals default_offline_state = CPU_STATE_OFFLINE; | |
48 | ||
4cc7ecb7 | 49 | static bool cede_offline_enabled __read_mostly = true; |
3aa565f5 GS |
50 | |
51 | /* | |
52 | * Enable/disable cede_offline when available. | |
53 | */ | |
54 | static int __init setup_cede_offline(char *str) | |
55 | { | |
4cc7ecb7 | 56 | return (kstrtobool(str, &cede_offline_enabled) == 0); |
3aa565f5 GS |
57 | } |
58 | ||
59 | __setup("cede_offline=", setup_cede_offline); | |
60 | ||
61 | enum cpu_state_vals get_cpu_current_state(int cpu) | |
62 | { | |
63 | return per_cpu(current_state, cpu); | |
64 | } | |
65 | ||
66 | void set_cpu_current_state(int cpu, enum cpu_state_vals state) | |
67 | { | |
68 | per_cpu(current_state, cpu) = state; | |
69 | } | |
70 | ||
71 | enum cpu_state_vals get_preferred_offline_state(int cpu) | |
72 | { | |
73 | return per_cpu(preferred_offline_state, cpu); | |
74 | } | |
75 | ||
76 | void set_preferred_offline_state(int cpu, enum cpu_state_vals state) | |
77 | { | |
78 | per_cpu(preferred_offline_state, cpu) = state; | |
79 | } | |
80 | ||
81 | void set_default_offline_state(int cpu) | |
82 | { | |
83 | per_cpu(preferred_offline_state, cpu) = default_offline_state; | |
84 | } | |
85 | ||
04da6af9 | 86 | static void rtas_stop_self(void) |
0332c2d4 | 87 | { |
b2e8590f | 88 | static struct rtas_args args; |
4fb8d027 | 89 | |
0332c2d4 ME |
90 | local_irq_disable(); |
91 | ||
41dd03a9 | 92 | BUG_ON(rtas_stop_self_token == RTAS_UNKNOWN_SERVICE); |
0332c2d4 ME |
93 | |
94 | printk("cpu %u (hwid %u) Ready to die...\n", | |
95 | smp_processor_id(), hard_smp_processor_id()); | |
b2e8590f ME |
96 | |
97 | rtas_call_unlocked(&args, rtas_stop_self_token, 0, 1, NULL); | |
0332c2d4 ME |
98 | |
99 | panic("Alas, I survived.\n"); | |
100 | } | |
101 | ||
06ba30b6 | 102 | static void pseries_mach_cpu_die(void) |
04da6af9 | 103 | { |
3aa565f5 GS |
104 | unsigned int cpu = smp_processor_id(); |
105 | unsigned int hwcpu = hard_smp_processor_id(); | |
106 | u8 cede_latency_hint = 0; | |
107 | ||
04da6af9 ME |
108 | local_irq_disable(); |
109 | idle_task_exit(); | |
eac1e731 CLG |
110 | if (xive_enabled()) |
111 | xive_teardown_cpu(); | |
112 | else | |
113 | xics_teardown_cpu(); | |
3aa565f5 GS |
114 | |
115 | if (get_preferred_offline_state(cpu) == CPU_STATE_INACTIVE) { | |
116 | set_cpu_current_state(cpu, CPU_STATE_INACTIVE); | |
32d8ad4e BK |
117 | if (ppc_md.suspend_disable_cpu) |
118 | ppc_md.suspend_disable_cpu(); | |
119 | ||
3aa565f5 GS |
120 | cede_latency_hint = 2; |
121 | ||
122 | get_lppaca()->idle = 1; | |
f13c13a0 | 123 | if (!lppaca_shared_proc(get_lppaca())) |
3aa565f5 GS |
124 | get_lppaca()->donate_dedicated_cpu = 1; |
125 | ||
3aa565f5 | 126 | while (get_preferred_offline_state(cpu) == CPU_STATE_INACTIVE) { |
fb912568 LZ |
127 | while (!prep_irq_for_idle()) { |
128 | local_irq_enable(); | |
129 | local_irq_disable(); | |
130 | } | |
131 | ||
3aa565f5 | 132 | extended_cede_processor(cede_latency_hint); |
3aa565f5 GS |
133 | } |
134 | ||
fb912568 LZ |
135 | local_irq_disable(); |
136 | ||
f13c13a0 | 137 | if (!lppaca_shared_proc(get_lppaca())) |
3aa565f5 GS |
138 | get_lppaca()->donate_dedicated_cpu = 0; |
139 | get_lppaca()->idle = 0; | |
3aa565f5 | 140 | |
0212f260 | 141 | if (get_preferred_offline_state(cpu) == CPU_STATE_ONLINE) { |
598c8231 | 142 | unregister_slb_shadow(hwcpu); |
3aa565f5 | 143 | |
fb912568 | 144 | hard_irq_disable(); |
0212f260 VS |
145 | /* |
146 | * Call to start_secondary_resume() will not return. | |
147 | * Kernel stack will be reset and start_secondary() | |
148 | * will be called to continue the online operation. | |
149 | */ | |
150 | start_secondary_resume(); | |
151 | } | |
152 | } | |
3aa565f5 | 153 | |
0212f260 VS |
154 | /* Requested state is CPU_STATE_OFFLINE at this point */ |
155 | WARN_ON(get_preferred_offline_state(cpu) != CPU_STATE_OFFLINE); | |
3aa565f5 | 156 | |
0212f260 | 157 | set_cpu_current_state(cpu, CPU_STATE_OFFLINE); |
598c8231 | 158 | unregister_slb_shadow(hwcpu); |
0212f260 | 159 | rtas_stop_self(); |
3aa565f5 | 160 | |
04da6af9 ME |
161 | /* Should never get here... */ |
162 | BUG(); | |
163 | for(;;); | |
164 | } | |
165 | ||
06ba30b6 | 166 | static int pseries_cpu_disable(void) |
413f7c40 ME |
167 | { |
168 | int cpu = smp_processor_id(); | |
169 | ||
ea0f1cab | 170 | set_cpu_online(cpu, false); |
413f7c40 ME |
171 | vdso_data->processorCount--; |
172 | ||
173 | /*fix boot_cpuid here*/ | |
174 | if (cpu == boot_cpuid) | |
8729faaa | 175 | boot_cpuid = cpumask_any(cpu_online_mask); |
413f7c40 ME |
176 | |
177 | /* FIXME: abstract this to not be platform specific later on */ | |
eac1e731 CLG |
178 | if (xive_enabled()) |
179 | xive_smp_disable_cpu(); | |
180 | else | |
181 | xics_migrate_irqs_away(); | |
413f7c40 ME |
182 | return 0; |
183 | } | |
184 | ||
3aa565f5 GS |
185 | /* |
186 | * pseries_cpu_die: Wait for the cpu to die. | |
187 | * @cpu: logical processor id of the CPU whose death we're awaiting. | |
188 | * | |
189 | * This function is called from the context of the thread which is performing | |
190 | * the cpu-offline. Here we wait for long enough to allow the cpu in question | |
191 | * to self-destroy so that the cpu-offline thread can send the CPU_DEAD | |
192 | * notifications. | |
193 | * | |
194 | * OTOH, pseries_mach_cpu_die() is called by the @cpu when it wants to | |
195 | * self-destruct. | |
196 | */ | |
06ba30b6 | 197 | static void pseries_cpu_die(unsigned int cpu) |
413f7c40 ME |
198 | { |
199 | int tries; | |
3aa565f5 | 200 | int cpu_status = 1; |
413f7c40 ME |
201 | unsigned int pcpu = get_hard_smp_processor_id(cpu); |
202 | ||
3aa565f5 GS |
203 | if (get_preferred_offline_state(cpu) == CPU_STATE_INACTIVE) { |
204 | cpu_status = 1; | |
940ce422 | 205 | for (tries = 0; tries < 5000; tries++) { |
3aa565f5 GS |
206 | if (get_cpu_current_state(cpu) == CPU_STATE_INACTIVE) { |
207 | cpu_status = 0; | |
208 | break; | |
209 | } | |
940ce422 | 210 | msleep(1); |
3aa565f5 GS |
211 | } |
212 | } else if (get_preferred_offline_state(cpu) == CPU_STATE_OFFLINE) { | |
213 | ||
214 | for (tries = 0; tries < 25; tries++) { | |
f8b67691 MN |
215 | cpu_status = smp_query_cpu_stopped(pcpu); |
216 | if (cpu_status == QCSS_STOPPED || | |
217 | cpu_status == QCSS_HARDWARE_ERROR) | |
3aa565f5 GS |
218 | break; |
219 | cpu_relax(); | |
220 | } | |
413f7c40 | 221 | } |
3aa565f5 | 222 | |
413f7c40 ME |
223 | if (cpu_status != 0) { |
224 | printk("Querying DEAD? cpu %i (%i) shows %i\n", | |
225 | cpu, pcpu, cpu_status); | |
226 | } | |
227 | ||
25985edc | 228 | /* Isolation and deallocation are definitely done by |
413f7c40 ME |
229 | * drslot_chrp_cpu. If they were not they would be |
230 | * done here. Change isolate state to Isolate and | |
231 | * change allocation-state to Unusable. | |
232 | */ | |
d2e60075 | 233 | paca_ptrs[cpu]->cpu_start = 0; |
413f7c40 ME |
234 | } |
235 | ||
236 | /* | |
828a6986 | 237 | * Update cpu_present_mask and paca(s) for a new cpu node. The wrinkle |
413f7c40 ME |
238 | * here is that a cpu device node may represent up to two logical cpus |
239 | * in the SMT case. We must honor the assumption in other code that | |
240 | * the logical ids for sibling SMT threads x and y are adjacent, such | |
241 | * that x^1 == y and y^1 == x. | |
242 | */ | |
06ba30b6 | 243 | static int pseries_add_processor(struct device_node *np) |
413f7c40 ME |
244 | { |
245 | unsigned int cpu; | |
8729faaa | 246 | cpumask_var_t candidate_mask, tmp; |
413f7c40 | 247 | int err = -ENOSPC, len, nthreads, i; |
d6f1e7ab | 248 | const __be32 *intserv; |
413f7c40 | 249 | |
e2eb6392 | 250 | intserv = of_get_property(np, "ibm,ppc-interrupt-server#s", &len); |
413f7c40 ME |
251 | if (!intserv) |
252 | return 0; | |
253 | ||
8729faaa AB |
254 | zalloc_cpumask_var(&candidate_mask, GFP_KERNEL); |
255 | zalloc_cpumask_var(&tmp, GFP_KERNEL); | |
256 | ||
413f7c40 ME |
257 | nthreads = len / sizeof(u32); |
258 | for (i = 0; i < nthreads; i++) | |
8729faaa | 259 | cpumask_set_cpu(i, tmp); |
413f7c40 | 260 | |
86ef5c9a | 261 | cpu_maps_update_begin(); |
413f7c40 | 262 | |
8729faaa | 263 | BUG_ON(!cpumask_subset(cpu_present_mask, cpu_possible_mask)); |
413f7c40 ME |
264 | |
265 | /* Get a bitmap of unoccupied slots. */ | |
8729faaa AB |
266 | cpumask_xor(candidate_mask, cpu_possible_mask, cpu_present_mask); |
267 | if (cpumask_empty(candidate_mask)) { | |
413f7c40 ME |
268 | /* If we get here, it most likely means that NR_CPUS is |
269 | * less than the partition's max processors setting. | |
270 | */ | |
b7c670d6 RH |
271 | printk(KERN_ERR "Cannot add cpu %pOF; this system configuration" |
272 | " supports %d logical cpus.\n", np, | |
53a448c3 | 273 | num_possible_cpus()); |
413f7c40 ME |
274 | goto out_unlock; |
275 | } | |
276 | ||
8729faaa AB |
277 | while (!cpumask_empty(tmp)) |
278 | if (cpumask_subset(tmp, candidate_mask)) | |
413f7c40 ME |
279 | /* Found a range where we can insert the new cpu(s) */ |
280 | break; | |
281 | else | |
8729faaa | 282 | cpumask_shift_left(tmp, tmp, nthreads); |
413f7c40 | 283 | |
8729faaa | 284 | if (cpumask_empty(tmp)) { |
828a6986 | 285 | printk(KERN_ERR "Unable to find space in cpu_present_mask for" |
b9ef7b4b | 286 | " processor %pOFn with %d thread(s)\n", np, |
413f7c40 ME |
287 | nthreads); |
288 | goto out_unlock; | |
289 | } | |
290 | ||
8729faaa | 291 | for_each_cpu(cpu, tmp) { |
104699c0 | 292 | BUG_ON(cpu_present(cpu)); |
ea0f1cab | 293 | set_cpu_present(cpu, true); |
d6f1e7ab | 294 | set_hard_smp_processor_id(cpu, be32_to_cpu(*intserv++)); |
413f7c40 ME |
295 | } |
296 | err = 0; | |
297 | out_unlock: | |
86ef5c9a | 298 | cpu_maps_update_done(); |
8729faaa AB |
299 | free_cpumask_var(candidate_mask); |
300 | free_cpumask_var(tmp); | |
413f7c40 ME |
301 | return err; |
302 | } | |
303 | ||
304 | /* | |
305 | * Update the present map for a cpu node which is going away, and set | |
306 | * the hard id in the paca(s) to -1 to be consistent with boot time | |
307 | * convention for non-present cpus. | |
308 | */ | |
06ba30b6 | 309 | static void pseries_remove_processor(struct device_node *np) |
413f7c40 ME |
310 | { |
311 | unsigned int cpu; | |
312 | int len, nthreads, i; | |
e36d1227 TF |
313 | const __be32 *intserv; |
314 | u32 thread; | |
413f7c40 | 315 | |
e2eb6392 | 316 | intserv = of_get_property(np, "ibm,ppc-interrupt-server#s", &len); |
413f7c40 ME |
317 | if (!intserv) |
318 | return; | |
319 | ||
320 | nthreads = len / sizeof(u32); | |
321 | ||
86ef5c9a | 322 | cpu_maps_update_begin(); |
413f7c40 | 323 | for (i = 0; i < nthreads; i++) { |
e36d1227 | 324 | thread = be32_to_cpu(intserv[i]); |
413f7c40 | 325 | for_each_present_cpu(cpu) { |
e36d1227 | 326 | if (get_hard_smp_processor_id(cpu) != thread) |
413f7c40 ME |
327 | continue; |
328 | BUG_ON(cpu_online(cpu)); | |
ea0f1cab | 329 | set_cpu_present(cpu, false); |
413f7c40 | 330 | set_hard_smp_processor_id(cpu, -1); |
1d9a0907 | 331 | update_numa_cpu_lookup_table(cpu, -1); |
413f7c40 ME |
332 | break; |
333 | } | |
8729faaa | 334 | if (cpu >= nr_cpu_ids) |
413f7c40 | 335 | printk(KERN_WARNING "Could not find cpu to remove " |
e36d1227 | 336 | "with physical id 0x%x\n", thread); |
413f7c40 | 337 | } |
86ef5c9a | 338 | cpu_maps_update_done(); |
413f7c40 ME |
339 | } |
340 | ||
183deeea NF |
341 | static int dlpar_online_cpu(struct device_node *dn) |
342 | { | |
343 | int rc = 0; | |
344 | unsigned int cpu; | |
345 | int len, nthreads, i; | |
346 | const __be32 *intserv; | |
347 | u32 thread; | |
348 | ||
349 | intserv = of_get_property(dn, "ibm,ppc-interrupt-server#s", &len); | |
350 | if (!intserv) | |
351 | return -EINVAL; | |
352 | ||
353 | nthreads = len / sizeof(u32); | |
354 | ||
355 | cpu_maps_update_begin(); | |
356 | for (i = 0; i < nthreads; i++) { | |
357 | thread = be32_to_cpu(intserv[i]); | |
358 | for_each_present_cpu(cpu) { | |
359 | if (get_hard_smp_processor_id(cpu) != thread) | |
360 | continue; | |
361 | BUG_ON(get_cpu_current_state(cpu) | |
362 | != CPU_STATE_OFFLINE); | |
363 | cpu_maps_update_done(); | |
cee5405d | 364 | timed_topology_update(1); |
e67e02a5 | 365 | find_and_online_cpu_nid(cpu); |
183deeea NF |
366 | rc = device_online(get_cpu_device(cpu)); |
367 | if (rc) | |
368 | goto out; | |
369 | cpu_maps_update_begin(); | |
370 | ||
371 | break; | |
372 | } | |
373 | if (cpu == num_possible_cpus()) | |
374 | printk(KERN_WARNING "Could not find cpu to online " | |
375 | "with physical id 0x%x\n", thread); | |
376 | } | |
377 | cpu_maps_update_done(); | |
378 | ||
379 | out: | |
380 | return rc; | |
381 | ||
382 | } | |
383 | ||
384 | static bool dlpar_cpu_exists(struct device_node *parent, u32 drc_index) | |
385 | { | |
386 | struct device_node *child = NULL; | |
387 | u32 my_drc_index; | |
388 | bool found; | |
389 | int rc; | |
390 | ||
391 | /* Assume cpu doesn't exist */ | |
392 | found = false; | |
393 | ||
394 | for_each_child_of_node(parent, child) { | |
395 | rc = of_property_read_u32(child, "ibm,my-drc-index", | |
396 | &my_drc_index); | |
397 | if (rc) | |
398 | continue; | |
399 | ||
400 | if (my_drc_index == drc_index) { | |
401 | of_node_put(child); | |
402 | found = true; | |
403 | break; | |
404 | } | |
405 | } | |
406 | ||
407 | return found; | |
408 | } | |
409 | ||
90edf184 NF |
410 | static bool valid_cpu_drc_index(struct device_node *parent, u32 drc_index) |
411 | { | |
412 | bool found = false; | |
413 | int rc, index; | |
414 | ||
415 | index = 0; | |
416 | while (!found) { | |
417 | u32 drc; | |
418 | ||
419 | rc = of_property_read_u32_index(parent, "ibm,drc-indexes", | |
420 | index++, &drc); | |
421 | if (rc) | |
422 | break; | |
423 | ||
424 | if (drc == drc_index) | |
425 | found = true; | |
426 | } | |
427 | ||
428 | return found; | |
429 | } | |
430 | ||
d98389f3 | 431 | static ssize_t dlpar_cpu_add(u32 drc_index) |
183deeea NF |
432 | { |
433 | struct device_node *dn, *parent; | |
e666ae0b NF |
434 | int rc, saved_rc; |
435 | ||
436 | pr_debug("Attempting to add CPU, drc index: %x\n", drc_index); | |
183deeea | 437 | |
183deeea | 438 | parent = of_find_node_by_path("/cpus"); |
e666ae0b NF |
439 | if (!parent) { |
440 | pr_warn("Failed to find CPU root node \"/cpus\"\n"); | |
183deeea | 441 | return -ENODEV; |
e666ae0b | 442 | } |
183deeea NF |
443 | |
444 | if (dlpar_cpu_exists(parent, drc_index)) { | |
445 | of_node_put(parent); | |
e666ae0b | 446 | pr_warn("CPU with drc index %x already exists\n", drc_index); |
183deeea NF |
447 | return -EINVAL; |
448 | } | |
449 | ||
90edf184 NF |
450 | if (!valid_cpu_drc_index(parent, drc_index)) { |
451 | of_node_put(parent); | |
452 | pr_warn("Cannot find CPU (drc index %x) to add.\n", drc_index); | |
453 | return -EINVAL; | |
454 | } | |
455 | ||
183deeea NF |
456 | rc = dlpar_acquire_drc(drc_index); |
457 | if (rc) { | |
e666ae0b NF |
458 | pr_warn("Failed to acquire DRC, rc: %d, drc index: %x\n", |
459 | rc, drc_index); | |
183deeea NF |
460 | of_node_put(parent); |
461 | return -EINVAL; | |
462 | } | |
463 | ||
464 | dn = dlpar_configure_connector(cpu_to_be32(drc_index), parent); | |
e666ae0b NF |
465 | if (!dn) { |
466 | pr_warn("Failed call to configure-connector, drc index: %x\n", | |
467 | drc_index); | |
468 | dlpar_release_drc(drc_index); | |
087ff6a5 | 469 | of_node_put(parent); |
183deeea | 470 | return -EINVAL; |
e666ae0b | 471 | } |
183deeea | 472 | |
215ee763 | 473 | rc = dlpar_attach_node(dn, parent); |
087ff6a5 TD |
474 | |
475 | /* Regardless we are done with parent now */ | |
476 | of_node_put(parent); | |
477 | ||
183deeea | 478 | if (rc) { |
e666ae0b | 479 | saved_rc = rc; |
b9ef7b4b RH |
480 | pr_warn("Failed to attach node %pOFn, rc: %d, drc index: %x\n", |
481 | dn, rc, drc_index); | |
e666ae0b NF |
482 | |
483 | rc = dlpar_release_drc(drc_index); | |
484 | if (!rc) | |
485 | dlpar_free_cc_nodes(dn); | |
486 | ||
487 | return saved_rc; | |
183deeea NF |
488 | } |
489 | ||
490 | rc = dlpar_online_cpu(dn); | |
e666ae0b NF |
491 | if (rc) { |
492 | saved_rc = rc; | |
b9ef7b4b RH |
493 | pr_warn("Failed to online cpu %pOFn, rc: %d, drc index: %x\n", |
494 | dn, rc, drc_index); | |
e666ae0b NF |
495 | |
496 | rc = dlpar_detach_node(dn); | |
497 | if (!rc) | |
498 | dlpar_release_drc(drc_index); | |
499 | ||
500 | return saved_rc; | |
501 | } | |
502 | ||
b9ef7b4b | 503 | pr_debug("Successfully added CPU %pOFn, drc index: %x\n", dn, |
e666ae0b | 504 | drc_index); |
d98389f3 | 505 | return rc; |
183deeea NF |
506 | } |
507 | ||
508 | static int dlpar_offline_cpu(struct device_node *dn) | |
509 | { | |
510 | int rc = 0; | |
511 | unsigned int cpu; | |
512 | int len, nthreads, i; | |
513 | const __be32 *intserv; | |
514 | u32 thread; | |
515 | ||
516 | intserv = of_get_property(dn, "ibm,ppc-interrupt-server#s", &len); | |
517 | if (!intserv) | |
518 | return -EINVAL; | |
519 | ||
520 | nthreads = len / sizeof(u32); | |
521 | ||
522 | cpu_maps_update_begin(); | |
523 | for (i = 0; i < nthreads; i++) { | |
524 | thread = be32_to_cpu(intserv[i]); | |
525 | for_each_present_cpu(cpu) { | |
526 | if (get_hard_smp_processor_id(cpu) != thread) | |
527 | continue; | |
528 | ||
529 | if (get_cpu_current_state(cpu) == CPU_STATE_OFFLINE) | |
530 | break; | |
531 | ||
532 | if (get_cpu_current_state(cpu) == CPU_STATE_ONLINE) { | |
533 | set_preferred_offline_state(cpu, | |
534 | CPU_STATE_OFFLINE); | |
535 | cpu_maps_update_done(); | |
cee5405d | 536 | timed_topology_update(1); |
183deeea NF |
537 | rc = device_offline(get_cpu_device(cpu)); |
538 | if (rc) | |
539 | goto out; | |
540 | cpu_maps_update_begin(); | |
541 | break; | |
542 | ||
543 | } | |
544 | ||
545 | /* | |
546 | * The cpu is in CPU_STATE_INACTIVE. | |
547 | * Upgrade it's state to CPU_STATE_OFFLINE. | |
548 | */ | |
549 | set_preferred_offline_state(cpu, CPU_STATE_OFFLINE); | |
550 | BUG_ON(plpar_hcall_norets(H_PROD, thread) | |
551 | != H_SUCCESS); | |
552 | __cpu_die(cpu); | |
553 | break; | |
554 | } | |
555 | if (cpu == num_possible_cpus()) | |
556 | printk(KERN_WARNING "Could not find cpu to offline with physical id 0x%x\n", thread); | |
557 | } | |
558 | cpu_maps_update_done(); | |
559 | ||
560 | out: | |
561 | return rc; | |
562 | ||
563 | } | |
564 | ||
d98389f3 NF |
565 | static ssize_t dlpar_cpu_remove(struct device_node *dn, u32 drc_index) |
566 | { | |
567 | int rc; | |
568 | ||
b9ef7b4b RH |
569 | pr_debug("Attempting to remove CPU %pOFn, drc index: %x\n", |
570 | dn, drc_index); | |
e666ae0b | 571 | |
d98389f3 | 572 | rc = dlpar_offline_cpu(dn); |
e666ae0b | 573 | if (rc) { |
b9ef7b4b | 574 | pr_warn("Failed to offline CPU %pOFn, rc: %d\n", dn, rc); |
d98389f3 | 575 | return -EINVAL; |
e666ae0b | 576 | } |
d98389f3 NF |
577 | |
578 | rc = dlpar_release_drc(drc_index); | |
e666ae0b | 579 | if (rc) { |
b9ef7b4b RH |
580 | pr_warn("Failed to release drc (%x) for CPU %pOFn, rc: %d\n", |
581 | drc_index, dn, rc); | |
e666ae0b | 582 | dlpar_online_cpu(dn); |
d98389f3 | 583 | return rc; |
e666ae0b | 584 | } |
d98389f3 NF |
585 | |
586 | rc = dlpar_detach_node(dn); | |
e666ae0b NF |
587 | if (rc) { |
588 | int saved_rc = rc; | |
d98389f3 | 589 | |
b9ef7b4b | 590 | pr_warn("Failed to detach CPU %pOFn, rc: %d", dn, rc); |
e666ae0b NF |
591 | |
592 | rc = dlpar_acquire_drc(drc_index); | |
593 | if (!rc) | |
594 | dlpar_online_cpu(dn); | |
595 | ||
596 | return saved_rc; | |
597 | } | |
598 | ||
599 | pr_debug("Successfully removed CPU, drc index: %x\n", drc_index); | |
600 | return 0; | |
d98389f3 NF |
601 | } |
602 | ||
ac713800 NF |
603 | static struct device_node *cpu_drc_index_to_dn(u32 drc_index) |
604 | { | |
605 | struct device_node *dn; | |
606 | u32 my_index; | |
607 | int rc; | |
608 | ||
609 | for_each_node_by_type(dn, "cpu") { | |
610 | rc = of_property_read_u32(dn, "ibm,my-drc-index", &my_index); | |
611 | if (rc) | |
612 | continue; | |
613 | ||
614 | if (my_index == drc_index) | |
615 | break; | |
616 | } | |
617 | ||
618 | return dn; | |
619 | } | |
620 | ||
621 | static int dlpar_cpu_remove_by_index(u32 drc_index) | |
622 | { | |
623 | struct device_node *dn; | |
624 | int rc; | |
625 | ||
626 | dn = cpu_drc_index_to_dn(drc_index); | |
627 | if (!dn) { | |
628 | pr_warn("Cannot find CPU (drc index %x) to remove\n", | |
629 | drc_index); | |
630 | return -ENODEV; | |
631 | } | |
632 | ||
633 | rc = dlpar_cpu_remove(dn, drc_index); | |
634 | of_node_put(dn); | |
635 | return rc; | |
636 | } | |
637 | ||
638 | static int find_dlpar_cpus_to_remove(u32 *cpu_drcs, int cpus_to_remove) | |
639 | { | |
640 | struct device_node *dn; | |
641 | int cpus_found = 0; | |
642 | int rc; | |
643 | ||
644 | /* We want to find cpus_to_remove + 1 CPUs to ensure we do not | |
645 | * remove the last CPU. | |
646 | */ | |
647 | for_each_node_by_type(dn, "cpu") { | |
648 | cpus_found++; | |
649 | ||
650 | if (cpus_found > cpus_to_remove) { | |
651 | of_node_put(dn); | |
652 | break; | |
653 | } | |
654 | ||
655 | /* Note that cpus_found is always 1 ahead of the index | |
656 | * into the cpu_drcs array, so we use cpus_found - 1 | |
657 | */ | |
658 | rc = of_property_read_u32(dn, "ibm,my-drc-index", | |
659 | &cpu_drcs[cpus_found - 1]); | |
660 | if (rc) { | |
b9ef7b4b RH |
661 | pr_warn("Error occurred getting drc-index for %pOFn\n", |
662 | dn); | |
ac713800 NF |
663 | of_node_put(dn); |
664 | return -1; | |
665 | } | |
666 | } | |
667 | ||
668 | if (cpus_found < cpus_to_remove) { | |
669 | pr_warn("Failed to find enough CPUs (%d of %d) to remove\n", | |
670 | cpus_found, cpus_to_remove); | |
671 | } else if (cpus_found == cpus_to_remove) { | |
672 | pr_warn("Cannot remove all CPUs\n"); | |
673 | } | |
674 | ||
675 | return cpus_found; | |
676 | } | |
677 | ||
678 | static int dlpar_cpu_remove_by_count(u32 cpus_to_remove) | |
679 | { | |
680 | u32 *cpu_drcs; | |
681 | int cpus_found; | |
682 | int cpus_removed = 0; | |
683 | int i, rc; | |
684 | ||
685 | pr_debug("Attempting to hot-remove %d CPUs\n", cpus_to_remove); | |
686 | ||
687 | cpu_drcs = kcalloc(cpus_to_remove, sizeof(*cpu_drcs), GFP_KERNEL); | |
688 | if (!cpu_drcs) | |
689 | return -EINVAL; | |
690 | ||
691 | cpus_found = find_dlpar_cpus_to_remove(cpu_drcs, cpus_to_remove); | |
692 | if (cpus_found <= cpus_to_remove) { | |
693 | kfree(cpu_drcs); | |
694 | return -EINVAL; | |
695 | } | |
696 | ||
697 | for (i = 0; i < cpus_to_remove; i++) { | |
698 | rc = dlpar_cpu_remove_by_index(cpu_drcs[i]); | |
699 | if (rc) | |
700 | break; | |
701 | ||
702 | cpus_removed++; | |
703 | } | |
704 | ||
705 | if (cpus_removed != cpus_to_remove) { | |
706 | pr_warn("CPU hot-remove failed, adding back removed CPUs\n"); | |
707 | ||
708 | for (i = 0; i < cpus_removed; i++) | |
709 | dlpar_cpu_add(cpu_drcs[i]); | |
710 | ||
711 | rc = -EINVAL; | |
712 | } else { | |
713 | rc = 0; | |
714 | } | |
715 | ||
716 | kfree(cpu_drcs); | |
717 | return rc; | |
718 | } | |
719 | ||
90edf184 NF |
720 | static int find_dlpar_cpus_to_add(u32 *cpu_drcs, u32 cpus_to_add) |
721 | { | |
722 | struct device_node *parent; | |
723 | int cpus_found = 0; | |
724 | int index, rc; | |
725 | ||
726 | parent = of_find_node_by_path("/cpus"); | |
727 | if (!parent) { | |
728 | pr_warn("Could not find CPU root node in device tree\n"); | |
729 | kfree(cpu_drcs); | |
730 | return -1; | |
731 | } | |
732 | ||
733 | /* Search the ibm,drc-indexes array for possible CPU drcs to | |
734 | * add. Note that the format of the ibm,drc-indexes array is | |
735 | * the number of entries in the array followed by the array | |
736 | * of drc values so we start looking at index = 1. | |
737 | */ | |
738 | index = 1; | |
739 | while (cpus_found < cpus_to_add) { | |
740 | u32 drc; | |
741 | ||
742 | rc = of_property_read_u32_index(parent, "ibm,drc-indexes", | |
743 | index++, &drc); | |
744 | if (rc) | |
745 | break; | |
746 | ||
747 | if (dlpar_cpu_exists(parent, drc)) | |
748 | continue; | |
749 | ||
750 | cpu_drcs[cpus_found++] = drc; | |
751 | } | |
752 | ||
753 | of_node_put(parent); | |
754 | return cpus_found; | |
755 | } | |
756 | ||
757 | static int dlpar_cpu_add_by_count(u32 cpus_to_add) | |
758 | { | |
759 | u32 *cpu_drcs; | |
760 | int cpus_added = 0; | |
761 | int cpus_found; | |
762 | int i, rc; | |
763 | ||
764 | pr_debug("Attempting to hot-add %d CPUs\n", cpus_to_add); | |
765 | ||
766 | cpu_drcs = kcalloc(cpus_to_add, sizeof(*cpu_drcs), GFP_KERNEL); | |
767 | if (!cpu_drcs) | |
768 | return -EINVAL; | |
769 | ||
770 | cpus_found = find_dlpar_cpus_to_add(cpu_drcs, cpus_to_add); | |
771 | if (cpus_found < cpus_to_add) { | |
772 | pr_warn("Failed to find enough CPUs (%d of %d) to add\n", | |
773 | cpus_found, cpus_to_add); | |
774 | kfree(cpu_drcs); | |
775 | return -EINVAL; | |
776 | } | |
777 | ||
778 | for (i = 0; i < cpus_to_add; i++) { | |
779 | rc = dlpar_cpu_add(cpu_drcs[i]); | |
780 | if (rc) | |
781 | break; | |
782 | ||
783 | cpus_added++; | |
784 | } | |
785 | ||
786 | if (cpus_added < cpus_to_add) { | |
787 | pr_warn("CPU hot-add failed, removing any added CPUs\n"); | |
788 | ||
789 | for (i = 0; i < cpus_added; i++) | |
790 | dlpar_cpu_remove_by_index(cpu_drcs[i]); | |
791 | ||
792 | rc = -EINVAL; | |
793 | } else { | |
794 | rc = 0; | |
795 | } | |
796 | ||
797 | kfree(cpu_drcs); | |
798 | return rc; | |
799 | } | |
800 | ||
81b61324 NF |
801 | int dlpar_cpu_readd(int cpu) |
802 | { | |
803 | struct device_node *dn; | |
804 | struct device *dev; | |
805 | u32 drc_index; | |
806 | int rc; | |
807 | ||
808 | dev = get_cpu_device(cpu); | |
809 | dn = dev->of_node; | |
810 | ||
811 | rc = of_property_read_u32(dn, "ibm,my-drc-index", &drc_index); | |
812 | ||
813 | rc = dlpar_cpu_remove_by_index(drc_index); | |
814 | if (!rc) | |
815 | rc = dlpar_cpu_add(drc_index); | |
816 | ||
817 | return rc; | |
818 | } | |
819 | ||
ac713800 NF |
820 | int dlpar_cpu(struct pseries_hp_errorlog *hp_elog) |
821 | { | |
822 | u32 count, drc_index; | |
823 | int rc; | |
824 | ||
825 | count = hp_elog->_drc_u.drc_count; | |
826 | drc_index = hp_elog->_drc_u.drc_index; | |
827 | ||
828 | lock_device_hotplug(); | |
829 | ||
830 | switch (hp_elog->action) { | |
831 | case PSERIES_HP_ELOG_ACTION_REMOVE: | |
832 | if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) | |
833 | rc = dlpar_cpu_remove_by_count(count); | |
834 | else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) | |
835 | rc = dlpar_cpu_remove_by_index(drc_index); | |
836 | else | |
837 | rc = -EINVAL; | |
838 | break; | |
90edf184 NF |
839 | case PSERIES_HP_ELOG_ACTION_ADD: |
840 | if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) | |
841 | rc = dlpar_cpu_add_by_count(count); | |
842 | else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) | |
843 | rc = dlpar_cpu_add(drc_index); | |
844 | else | |
845 | rc = -EINVAL; | |
846 | break; | |
ac713800 NF |
847 | default: |
848 | pr_err("Invalid action (%d) specified\n", hp_elog->action); | |
849 | rc = -EINVAL; | |
850 | break; | |
851 | } | |
852 | ||
853 | unlock_device_hotplug(); | |
854 | return rc; | |
855 | } | |
856 | ||
d98389f3 NF |
857 | #ifdef CONFIG_ARCH_CPU_PROBE_RELEASE |
858 | ||
859 | static ssize_t dlpar_cpu_probe(const char *buf, size_t count) | |
860 | { | |
861 | u32 drc_index; | |
862 | int rc; | |
863 | ||
864 | rc = kstrtou32(buf, 0, &drc_index); | |
865 | if (rc) | |
866 | return -EINVAL; | |
867 | ||
868 | rc = dlpar_cpu_add(drc_index); | |
869 | ||
870 | return rc ? rc : count; | |
871 | } | |
872 | ||
183deeea NF |
873 | static ssize_t dlpar_cpu_release(const char *buf, size_t count) |
874 | { | |
875 | struct device_node *dn; | |
876 | u32 drc_index; | |
877 | int rc; | |
878 | ||
879 | dn = of_find_node_by_path(buf); | |
880 | if (!dn) | |
881 | return -EINVAL; | |
882 | ||
883 | rc = of_property_read_u32(dn, "ibm,my-drc-index", &drc_index); | |
884 | if (rc) { | |
885 | of_node_put(dn); | |
886 | return -EINVAL; | |
887 | } | |
888 | ||
d98389f3 | 889 | rc = dlpar_cpu_remove(dn, drc_index); |
183deeea NF |
890 | of_node_put(dn); |
891 | ||
d98389f3 | 892 | return rc ? rc : count; |
183deeea NF |
893 | } |
894 | ||
895 | #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ | |
896 | ||
06ba30b6 | 897 | static int pseries_smp_notifier(struct notifier_block *nb, |
f5242e5a | 898 | unsigned long action, void *data) |
413f7c40 | 899 | { |
f5242e5a | 900 | struct of_reconfig_data *rd = data; |
de2780a3 | 901 | int err = 0; |
413f7c40 ME |
902 | |
903 | switch (action) { | |
1cf3d8b3 | 904 | case OF_RECONFIG_ATTACH_NODE: |
f5242e5a | 905 | err = pseries_add_processor(rd->dn); |
413f7c40 | 906 | break; |
1cf3d8b3 | 907 | case OF_RECONFIG_DETACH_NODE: |
f5242e5a | 908 | pseries_remove_processor(rd->dn); |
413f7c40 | 909 | break; |
413f7c40 | 910 | } |
de2780a3 | 911 | return notifier_from_errno(err); |
413f7c40 ME |
912 | } |
913 | ||
06ba30b6 ME |
914 | static struct notifier_block pseries_smp_nb = { |
915 | .notifier_call = pseries_smp_notifier, | |
413f7c40 ME |
916 | }; |
917 | ||
3aa565f5 GS |
918 | #define MAX_CEDE_LATENCY_LEVELS 4 |
919 | #define CEDE_LATENCY_PARAM_LENGTH 10 | |
920 | #define CEDE_LATENCY_PARAM_MAX_LENGTH \ | |
921 | (MAX_CEDE_LATENCY_LEVELS * CEDE_LATENCY_PARAM_LENGTH * sizeof(char)) | |
922 | #define CEDE_LATENCY_TOKEN 45 | |
923 | ||
924 | static char cede_parameters[CEDE_LATENCY_PARAM_MAX_LENGTH]; | |
925 | ||
926 | static int parse_cede_parameters(void) | |
927 | { | |
3aa565f5 | 928 | memset(cede_parameters, 0, CEDE_LATENCY_PARAM_MAX_LENGTH); |
20a8ab97 AB |
929 | return rtas_call(rtas_token("ibm,get-system-parameter"), 3, 1, |
930 | NULL, | |
931 | CEDE_LATENCY_TOKEN, | |
932 | __pa(cede_parameters), | |
933 | CEDE_LATENCY_PARAM_MAX_LENGTH); | |
3aa565f5 GS |
934 | } |
935 | ||
0332c2d4 ME |
936 | static int __init pseries_cpu_hotplug_init(void) |
937 | { | |
3aa565f5 | 938 | int cpu; |
f8b67691 | 939 | int qcss_tok; |
64f27585 | 940 | |
183deeea NF |
941 | #ifdef CONFIG_ARCH_CPU_PROBE_RELEASE |
942 | ppc_md.cpu_probe = dlpar_cpu_probe; | |
943 | ppc_md.cpu_release = dlpar_cpu_release; | |
944 | #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ | |
945 | ||
41dd03a9 | 946 | rtas_stop_self_token = rtas_token("stop-self"); |
674fa677 | 947 | qcss_tok = rtas_token("query-cpu-stopped-state"); |
0332c2d4 | 948 | |
41dd03a9 | 949 | if (rtas_stop_self_token == RTAS_UNKNOWN_SERVICE || |
674fa677 ME |
950 | qcss_tok == RTAS_UNKNOWN_SERVICE) { |
951 | printk(KERN_INFO "CPU Hotplug not supported by firmware " | |
952 | "- disabling.\n"); | |
953 | return 0; | |
954 | } | |
04da6af9 | 955 | |
06ba30b6 ME |
956 | ppc_md.cpu_die = pseries_mach_cpu_die; |
957 | smp_ops->cpu_disable = pseries_cpu_disable; | |
958 | smp_ops->cpu_die = pseries_cpu_die; | |
413f7c40 ME |
959 | |
960 | /* Processors can be added/removed only on LPAR */ | |
3aa565f5 | 961 | if (firmware_has_feature(FW_FEATURE_LPAR)) { |
1cf3d8b3 | 962 | of_reconfig_notifier_register(&pseries_smp_nb); |
3aa565f5 GS |
963 | cpu_maps_update_begin(); |
964 | if (cede_offline_enabled && parse_cede_parameters() == 0) { | |
965 | default_offline_state = CPU_STATE_INACTIVE; | |
966 | for_each_online_cpu(cpu) | |
967 | set_default_offline_state(cpu); | |
968 | } | |
969 | cpu_maps_update_done(); | |
970 | } | |
413f7c40 | 971 | |
0332c2d4 ME |
972 | return 0; |
973 | } | |
d2a36071 | 974 | machine_arch_initcall(pseries, pseries_cpu_hotplug_init); |