diff options
author | Michael Ellerman <mpe@ellerman.id.au> | 2015-04-13 01:29:36 -0400 |
---|---|---|
committer | Michael Ellerman <mpe@ellerman.id.au> | 2015-04-13 01:30:21 -0400 |
commit | 3a29dd6d6f445212ddbcf43a2ba6352127ce9ee8 (patch) | |
tree | e05aad022c46d0550f2c9dc8d4361a6634591852 /arch/powerpc/platforms/pseries | |
parent | f7e9e358362557c3aa2c1ec47490f29fe880a09e (diff) | |
parent | 51925fb3c5c901aa06cdc853268a6e19e19bcdc7 (diff) |
Merge branch 'next-dlpar' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc into next
Merge series from Nathan Fontenot to do memory hotplug in the kernel.
Diffstat (limited to 'arch/powerpc/platforms/pseries')
-rw-r--r-- | arch/powerpc/platforms/pseries/dlpar.c | 118 | ||||
-rw-r--r-- | arch/powerpc/platforms/pseries/hotplug-memory.c | 473 | ||||
-rw-r--r-- | arch/powerpc/platforms/pseries/pseries.h | 12 |
3 files changed, 601 insertions, 2 deletions
diff --git a/arch/powerpc/platforms/pseries/dlpar.c b/arch/powerpc/platforms/pseries/dlpar.c index c22bb1b4beb8..b4b11096ea8b 100644 --- a/arch/powerpc/platforms/pseries/dlpar.c +++ b/arch/powerpc/platforms/pseries/dlpar.c | |||
@@ -10,6 +10,8 @@ | |||
10 | * 2 as published by the Free Software Foundation. | 10 | * 2 as published by the Free Software Foundation. |
11 | */ | 11 | */ |
12 | 12 | ||
13 | #define pr_fmt(fmt) "dlpar: " fmt | ||
14 | |||
13 | #include <linux/kernel.h> | 15 | #include <linux/kernel.h> |
14 | #include <linux/notifier.h> | 16 | #include <linux/notifier.h> |
15 | #include <linux/spinlock.h> | 17 | #include <linux/spinlock.h> |
@@ -535,13 +537,125 @@ static ssize_t dlpar_cpu_release(const char *buf, size_t count) | |||
535 | return count; | 537 | return count; |
536 | } | 538 | } |
537 | 539 | ||
540 | #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ | ||
541 | |||
542 | static int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog) | ||
543 | { | ||
544 | int rc; | ||
545 | |||
546 | /* pseries error logs are in BE format, convert to cpu type */ | ||
547 | switch (hp_elog->id_type) { | ||
548 | case PSERIES_HP_ELOG_ID_DRC_COUNT: | ||
549 | hp_elog->_drc_u.drc_count = | ||
550 | be32_to_cpu(hp_elog->_drc_u.drc_count); | ||
551 | break; | ||
552 | case PSERIES_HP_ELOG_ID_DRC_INDEX: | ||
553 | hp_elog->_drc_u.drc_index = | ||
554 | be32_to_cpu(hp_elog->_drc_u.drc_index); | ||
555 | } | ||
556 | |||
557 | switch (hp_elog->resource) { | ||
558 | case PSERIES_HP_ELOG_RESOURCE_MEM: | ||
559 | rc = dlpar_memory(hp_elog); | ||
560 | break; | ||
561 | default: | ||
562 | pr_warn_ratelimited("Invalid resource (%d) specified\n", | ||
563 | hp_elog->resource); | ||
564 | rc = -EINVAL; | ||
565 | } | ||
566 | |||
567 | return rc; | ||
568 | } | ||
569 | |||
570 | static ssize_t dlpar_store(struct class *class, struct class_attribute *attr, | ||
571 | const char *buf, size_t count) | ||
572 | { | ||
573 | struct pseries_hp_errorlog *hp_elog; | ||
574 | const char *arg; | ||
575 | int rc; | ||
576 | |||
577 | hp_elog = kzalloc(sizeof(*hp_elog), GFP_KERNEL); | ||
578 | if (!hp_elog) { | ||
579 | rc = -ENOMEM; | ||
580 | goto dlpar_store_out; | ||
581 | } | ||
582 | |||
583 | /* Parse out the request from the user, this will be in the form | ||
584 | * <resource> <action> <id_type> <id> | ||
585 | */ | ||
586 | arg = buf; | ||
587 | if (!strncmp(arg, "memory", 6)) { | ||
588 | hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_MEM; | ||
589 | arg += strlen("memory "); | ||
590 | } else { | ||
591 | pr_err("Invalid resource specified: \"%s\"\n", buf); | ||
592 | rc = -EINVAL; | ||
593 | goto dlpar_store_out; | ||
594 | } | ||
595 | |||
596 | if (!strncmp(arg, "add", 3)) { | ||
597 | hp_elog->action = PSERIES_HP_ELOG_ACTION_ADD; | ||
598 | arg += strlen("add "); | ||
599 | } else if (!strncmp(arg, "remove", 6)) { | ||
600 | hp_elog->action = PSERIES_HP_ELOG_ACTION_REMOVE; | ||
601 | arg += strlen("remove "); | ||
602 | } else { | ||
603 | pr_err("Invalid action specified: \"%s\"\n", buf); | ||
604 | rc = -EINVAL; | ||
605 | goto dlpar_store_out; | ||
606 | } | ||
607 | |||
608 | if (!strncmp(arg, "index", 5)) { | ||
609 | u32 index; | ||
610 | |||
611 | hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_INDEX; | ||
612 | arg += strlen("index "); | ||
613 | if (kstrtou32(arg, 0, &index)) { | ||
614 | rc = -EINVAL; | ||
615 | pr_err("Invalid drc_index specified: \"%s\"\n", buf); | ||
616 | goto dlpar_store_out; | ||
617 | } | ||
618 | |||
619 | hp_elog->_drc_u.drc_index = cpu_to_be32(index); | ||
620 | } else if (!strncmp(arg, "count", 5)) { | ||
621 | u32 count; | ||
622 | |||
623 | hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_COUNT; | ||
624 | arg += strlen("count "); | ||
625 | if (kstrtou32(arg, 0, &count)) { | ||
626 | rc = -EINVAL; | ||
627 | pr_err("Invalid count specified: \"%s\"\n", buf); | ||
628 | goto dlpar_store_out; | ||
629 | } | ||
630 | |||
631 | hp_elog->_drc_u.drc_count = cpu_to_be32(count); | ||
632 | } else { | ||
633 | pr_err("Invalid id_type specified: \"%s\"\n", buf); | ||
634 | rc = -EINVAL; | ||
635 | goto dlpar_store_out; | ||
636 | } | ||
637 | |||
638 | rc = handle_dlpar_errorlog(hp_elog); | ||
639 | |||
640 | dlpar_store_out: | ||
641 | kfree(hp_elog); | ||
642 | return rc ? rc : count; | ||
643 | } | ||
644 | |||
645 | static CLASS_ATTR(dlpar, S_IWUSR, NULL, dlpar_store); | ||
646 | |||
538 | static int __init pseries_dlpar_init(void) | 647 | static int __init pseries_dlpar_init(void) |
539 | { | 648 | { |
649 | int rc; | ||
650 | |||
651 | #ifdef CONFIG_ARCH_CPU_PROBE_RELEASE | ||
540 | ppc_md.cpu_probe = dlpar_cpu_probe; | 652 | ppc_md.cpu_probe = dlpar_cpu_probe; |
541 | ppc_md.cpu_release = dlpar_cpu_release; | 653 | ppc_md.cpu_release = dlpar_cpu_release; |
654 | #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ | ||
542 | 655 | ||
543 | return 0; | 656 | rc = sysfs_create_file(kernel_kobj, &class_attr_dlpar.attr); |
657 | |||
658 | return rc; | ||
544 | } | 659 | } |
545 | machine_device_initcall(pseries, pseries_dlpar_init); | 660 | machine_device_initcall(pseries, pseries_dlpar_init); |
546 | 661 | ||
547 | #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ | ||
diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c index fa41f0da5b6f..742ef88ffd7b 100644 --- a/arch/powerpc/platforms/pseries/hotplug-memory.c +++ b/arch/powerpc/platforms/pseries/hotplug-memory.c | |||
@@ -9,11 +9,14 @@ | |||
9 | * 2 of the License, or (at your option) any later version. | 9 | * 2 of the License, or (at your option) any later version. |
10 | */ | 10 | */ |
11 | 11 | ||
12 | #define pr_fmt(fmt) "pseries-hotplug-mem: " fmt | ||
13 | |||
12 | #include <linux/of.h> | 14 | #include <linux/of.h> |
13 | #include <linux/of_address.h> | 15 | #include <linux/of_address.h> |
14 | #include <linux/memblock.h> | 16 | #include <linux/memblock.h> |
15 | #include <linux/memory.h> | 17 | #include <linux/memory.h> |
16 | #include <linux/memory_hotplug.h> | 18 | #include <linux/memory_hotplug.h> |
19 | #include <linux/slab.h> | ||
17 | 20 | ||
18 | #include <asm/firmware.h> | 21 | #include <asm/firmware.h> |
19 | #include <asm/machdep.h> | 22 | #include <asm/machdep.h> |
@@ -21,6 +24,8 @@ | |||
21 | #include <asm/sparsemem.h> | 24 | #include <asm/sparsemem.h> |
22 | #include "pseries.h" | 25 | #include "pseries.h" |
23 | 26 | ||
27 | static bool rtas_hp_event; | ||
28 | |||
24 | unsigned long pseries_memory_block_size(void) | 29 | unsigned long pseries_memory_block_size(void) |
25 | { | 30 | { |
26 | struct device_node *np; | 31 | struct device_node *np; |
@@ -64,6 +69,67 @@ unsigned long pseries_memory_block_size(void) | |||
64 | return memblock_size; | 69 | return memblock_size; |
65 | } | 70 | } |
66 | 71 | ||
72 | static void dlpar_free_drconf_property(struct property *prop) | ||
73 | { | ||
74 | kfree(prop->name); | ||
75 | kfree(prop->value); | ||
76 | kfree(prop); | ||
77 | } | ||
78 | |||
79 | static struct property *dlpar_clone_drconf_property(struct device_node *dn) | ||
80 | { | ||
81 | struct property *prop, *new_prop; | ||
82 | struct of_drconf_cell *lmbs; | ||
83 | u32 num_lmbs, *p; | ||
84 | int i; | ||
85 | |||
86 | prop = of_find_property(dn, "ibm,dynamic-memory", NULL); | ||
87 | if (!prop) | ||
88 | return NULL; | ||
89 | |||
90 | new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); | ||
91 | if (!new_prop) | ||
92 | return NULL; | ||
93 | |||
94 | new_prop->name = kstrdup(prop->name, GFP_KERNEL); | ||
95 | new_prop->value = kmalloc(prop->length, GFP_KERNEL); | ||
96 | if (!new_prop->name || !new_prop->value) { | ||
97 | dlpar_free_drconf_property(new_prop); | ||
98 | return NULL; | ||
99 | } | ||
100 | |||
101 | memcpy(new_prop->value, prop->value, prop->length); | ||
102 | new_prop->length = prop->length; | ||
103 | |||
104 | /* Convert the property to cpu endian-ness */ | ||
105 | p = new_prop->value; | ||
106 | *p = be32_to_cpu(*p); | ||
107 | |||
108 | num_lmbs = *p++; | ||
109 | lmbs = (struct of_drconf_cell *)p; | ||
110 | |||
111 | for (i = 0; i < num_lmbs; i++) { | ||
112 | lmbs[i].base_addr = be64_to_cpu(lmbs[i].base_addr); | ||
113 | lmbs[i].drc_index = be32_to_cpu(lmbs[i].drc_index); | ||
114 | lmbs[i].flags = be32_to_cpu(lmbs[i].flags); | ||
115 | } | ||
116 | |||
117 | return new_prop; | ||
118 | } | ||
119 | |||
120 | static struct memory_block *lmb_to_memblock(struct of_drconf_cell *lmb) | ||
121 | { | ||
122 | unsigned long section_nr; | ||
123 | struct mem_section *mem_sect; | ||
124 | struct memory_block *mem_block; | ||
125 | |||
126 | section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr)); | ||
127 | mem_sect = __nr_to_section(section_nr); | ||
128 | |||
129 | mem_block = find_memory_block(mem_sect); | ||
130 | return mem_block; | ||
131 | } | ||
132 | |||
67 | #ifdef CONFIG_MEMORY_HOTREMOVE | 133 | #ifdef CONFIG_MEMORY_HOTREMOVE |
68 | static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) | 134 | static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) |
69 | { | 135 | { |
@@ -122,6 +188,173 @@ static int pseries_remove_mem_node(struct device_node *np) | |||
122 | pseries_remove_memblock(base, lmb_size); | 188 | pseries_remove_memblock(base, lmb_size); |
123 | return 0; | 189 | return 0; |
124 | } | 190 | } |
191 | |||
192 | static bool lmb_is_removable(struct of_drconf_cell *lmb) | ||
193 | { | ||
194 | int i, scns_per_block; | ||
195 | int rc = 1; | ||
196 | unsigned long pfn, block_sz; | ||
197 | u64 phys_addr; | ||
198 | |||
199 | if (!(lmb->flags & DRCONF_MEM_ASSIGNED)) | ||
200 | return false; | ||
201 | |||
202 | block_sz = memory_block_size_bytes(); | ||
203 | scns_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE; | ||
204 | phys_addr = lmb->base_addr; | ||
205 | |||
206 | for (i = 0; i < scns_per_block; i++) { | ||
207 | pfn = PFN_DOWN(phys_addr); | ||
208 | if (!pfn_present(pfn)) | ||
209 | continue; | ||
210 | |||
211 | rc &= is_mem_section_removable(pfn, PAGES_PER_SECTION); | ||
212 | phys_addr += MIN_MEMORY_BLOCK_SIZE; | ||
213 | } | ||
214 | |||
215 | return rc ? true : false; | ||
216 | } | ||
217 | |||
218 | static int dlpar_add_lmb(struct of_drconf_cell *); | ||
219 | |||
220 | static int dlpar_remove_lmb(struct of_drconf_cell *lmb) | ||
221 | { | ||
222 | struct memory_block *mem_block; | ||
223 | unsigned long block_sz; | ||
224 | int nid, rc; | ||
225 | |||
226 | if (!lmb_is_removable(lmb)) | ||
227 | return -EINVAL; | ||
228 | |||
229 | mem_block = lmb_to_memblock(lmb); | ||
230 | if (!mem_block) | ||
231 | return -EINVAL; | ||
232 | |||
233 | rc = device_offline(&mem_block->dev); | ||
234 | put_device(&mem_block->dev); | ||
235 | if (rc) | ||
236 | return rc; | ||
237 | |||
238 | block_sz = pseries_memory_block_size(); | ||
239 | nid = memory_add_physaddr_to_nid(lmb->base_addr); | ||
240 | |||
241 | remove_memory(nid, lmb->base_addr, block_sz); | ||
242 | |||
243 | /* Update memory regions for memory remove */ | ||
244 | memblock_remove(lmb->base_addr, block_sz); | ||
245 | |||
246 | dlpar_release_drc(lmb->drc_index); | ||
247 | |||
248 | lmb->flags &= ~DRCONF_MEM_ASSIGNED; | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static int dlpar_memory_remove_by_count(u32 lmbs_to_remove, | ||
253 | struct property *prop) | ||
254 | { | ||
255 | struct of_drconf_cell *lmbs; | ||
256 | int lmbs_removed = 0; | ||
257 | int lmbs_available = 0; | ||
258 | u32 num_lmbs, *p; | ||
259 | int i, rc; | ||
260 | |||
261 | pr_info("Attempting to hot-remove %d LMB(s)\n", lmbs_to_remove); | ||
262 | |||
263 | if (lmbs_to_remove == 0) | ||
264 | return -EINVAL; | ||
265 | |||
266 | p = prop->value; | ||
267 | num_lmbs = *p++; | ||
268 | lmbs = (struct of_drconf_cell *)p; | ||
269 | |||
270 | /* Validate that there are enough LMBs to satisfy the request */ | ||
271 | for (i = 0; i < num_lmbs; i++) { | ||
272 | if (lmbs[i].flags & DRCONF_MEM_ASSIGNED) | ||
273 | lmbs_available++; | ||
274 | } | ||
275 | |||
276 | if (lmbs_available < lmbs_to_remove) | ||
277 | return -EINVAL; | ||
278 | |||
279 | for (i = 0; i < num_lmbs && lmbs_removed < lmbs_to_remove; i++) { | ||
280 | rc = dlpar_remove_lmb(&lmbs[i]); | ||
281 | if (rc) | ||
282 | continue; | ||
283 | |||
284 | lmbs_removed++; | ||
285 | |||
286 | /* Mark this lmb so we can add it later if all of the | ||
287 | * requested LMBs cannot be removed. | ||
288 | */ | ||
289 | lmbs[i].reserved = 1; | ||
290 | } | ||
291 | |||
292 | if (lmbs_removed != lmbs_to_remove) { | ||
293 | pr_err("Memory hot-remove failed, adding LMB's back\n"); | ||
294 | |||
295 | for (i = 0; i < num_lmbs; i++) { | ||
296 | if (!lmbs[i].reserved) | ||
297 | continue; | ||
298 | |||
299 | rc = dlpar_add_lmb(&lmbs[i]); | ||
300 | if (rc) | ||
301 | pr_err("Failed to add LMB back, drc index %x\n", | ||
302 | lmbs[i].drc_index); | ||
303 | |||
304 | lmbs[i].reserved = 0; | ||
305 | } | ||
306 | |||
307 | rc = -EINVAL; | ||
308 | } else { | ||
309 | for (i = 0; i < num_lmbs; i++) { | ||
310 | if (!lmbs[i].reserved) | ||
311 | continue; | ||
312 | |||
313 | pr_info("Memory at %llx was hot-removed\n", | ||
314 | lmbs[i].base_addr); | ||
315 | |||
316 | lmbs[i].reserved = 0; | ||
317 | } | ||
318 | rc = 0; | ||
319 | } | ||
320 | |||
321 | return rc; | ||
322 | } | ||
323 | |||
324 | static int dlpar_memory_remove_by_index(u32 drc_index, struct property *prop) | ||
325 | { | ||
326 | struct of_drconf_cell *lmbs; | ||
327 | u32 num_lmbs, *p; | ||
328 | int lmb_found; | ||
329 | int i, rc; | ||
330 | |||
331 | pr_info("Attempting to hot-remove LMB, drc index %x\n", drc_index); | ||
332 | |||
333 | p = prop->value; | ||
334 | num_lmbs = *p++; | ||
335 | lmbs = (struct of_drconf_cell *)p; | ||
336 | |||
337 | lmb_found = 0; | ||
338 | for (i = 0; i < num_lmbs; i++) { | ||
339 | if (lmbs[i].drc_index == drc_index) { | ||
340 | lmb_found = 1; | ||
341 | rc = dlpar_remove_lmb(&lmbs[i]); | ||
342 | break; | ||
343 | } | ||
344 | } | ||
345 | |||
346 | if (!lmb_found) | ||
347 | rc = -EINVAL; | ||
348 | |||
349 | if (rc) | ||
350 | pr_info("Failed to hot-remove memory at %llx\n", | ||
351 | lmbs[i].base_addr); | ||
352 | else | ||
353 | pr_info("Memory at %llx was hot-removed\n", lmbs[i].base_addr); | ||
354 | |||
355 | return rc; | ||
356 | } | ||
357 | |||
125 | #else | 358 | #else |
126 | static inline int pseries_remove_memblock(unsigned long base, | 359 | static inline int pseries_remove_memblock(unsigned long base, |
127 | unsigned int memblock_size) | 360 | unsigned int memblock_size) |
@@ -132,8 +365,245 @@ static inline int pseries_remove_mem_node(struct device_node *np) | |||
132 | { | 365 | { |
133 | return 0; | 366 | return 0; |
134 | } | 367 | } |
368 | static inline int dlpar_memory_remove(struct pseries_hp_errorlog *hp_elog) | ||
369 | { | ||
370 | return -EOPNOTSUPP; | ||
371 | } | ||
372 | |||
135 | #endif /* CONFIG_MEMORY_HOTREMOVE */ | 373 | #endif /* CONFIG_MEMORY_HOTREMOVE */ |
136 | 374 | ||
375 | static int dlpar_add_lmb(struct of_drconf_cell *lmb) | ||
376 | { | ||
377 | struct memory_block *mem_block; | ||
378 | unsigned long block_sz; | ||
379 | int nid, rc; | ||
380 | |||
381 | if (lmb->flags & DRCONF_MEM_ASSIGNED) | ||
382 | return -EINVAL; | ||
383 | |||
384 | block_sz = memory_block_size_bytes(); | ||
385 | |||
386 | rc = dlpar_acquire_drc(lmb->drc_index); | ||
387 | if (rc) | ||
388 | return rc; | ||
389 | |||
390 | /* Find the node id for this address */ | ||
391 | nid = memory_add_physaddr_to_nid(lmb->base_addr); | ||
392 | |||
393 | /* Add the memory */ | ||
394 | rc = add_memory(nid, lmb->base_addr, block_sz); | ||
395 | if (rc) { | ||
396 | dlpar_release_drc(lmb->drc_index); | ||
397 | return rc; | ||
398 | } | ||
399 | |||
400 | /* Register this block of memory */ | ||
401 | rc = memblock_add(lmb->base_addr, block_sz); | ||
402 | if (rc) { | ||
403 | remove_memory(nid, lmb->base_addr, block_sz); | ||
404 | dlpar_release_drc(lmb->drc_index); | ||
405 | return rc; | ||
406 | } | ||
407 | |||
408 | mem_block = lmb_to_memblock(lmb); | ||
409 | if (!mem_block) { | ||
410 | remove_memory(nid, lmb->base_addr, block_sz); | ||
411 | dlpar_release_drc(lmb->drc_index); | ||
412 | return -EINVAL; | ||
413 | } | ||
414 | |||
415 | rc = device_online(&mem_block->dev); | ||
416 | put_device(&mem_block->dev); | ||
417 | if (rc) { | ||
418 | remove_memory(nid, lmb->base_addr, block_sz); | ||
419 | dlpar_release_drc(lmb->drc_index); | ||
420 | return rc; | ||
421 | } | ||
422 | |||
423 | lmb->flags |= DRCONF_MEM_ASSIGNED; | ||
424 | return 0; | ||
425 | } | ||
426 | |||
427 | static int dlpar_memory_add_by_count(u32 lmbs_to_add, struct property *prop) | ||
428 | { | ||
429 | struct of_drconf_cell *lmbs; | ||
430 | u32 num_lmbs, *p; | ||
431 | int lmbs_available = 0; | ||
432 | int lmbs_added = 0; | ||
433 | int i, rc; | ||
434 | |||
435 | pr_info("Attempting to hot-add %d LMB(s)\n", lmbs_to_add); | ||
436 | |||
437 | if (lmbs_to_add == 0) | ||
438 | return -EINVAL; | ||
439 | |||
440 | p = prop->value; | ||
441 | num_lmbs = *p++; | ||
442 | lmbs = (struct of_drconf_cell *)p; | ||
443 | |||
444 | /* Validate that there are enough LMBs to satisfy the request */ | ||
445 | for (i = 0; i < num_lmbs; i++) { | ||
446 | if (!(lmbs[i].flags & DRCONF_MEM_ASSIGNED)) | ||
447 | lmbs_available++; | ||
448 | } | ||
449 | |||
450 | if (lmbs_available < lmbs_to_add) | ||
451 | return -EINVAL; | ||
452 | |||
453 | for (i = 0; i < num_lmbs && lmbs_to_add != lmbs_added; i++) { | ||
454 | rc = dlpar_add_lmb(&lmbs[i]); | ||
455 | if (rc) | ||
456 | continue; | ||
457 | |||
458 | lmbs_added++; | ||
459 | |||
460 | /* Mark this lmb so we can remove it later if all of the | ||
461 | * requested LMBs cannot be added. | ||
462 | */ | ||
463 | lmbs[i].reserved = 1; | ||
464 | } | ||
465 | |||
466 | if (lmbs_added != lmbs_to_add) { | ||
467 | pr_err("Memory hot-add failed, removing any added LMBs\n"); | ||
468 | |||
469 | for (i = 0; i < num_lmbs; i++) { | ||
470 | if (!lmbs[i].reserved) | ||
471 | continue; | ||
472 | |||
473 | rc = dlpar_remove_lmb(&lmbs[i]); | ||
474 | if (rc) | ||
475 | pr_err("Failed to remove LMB, drc index %x\n", | ||
476 | be32_to_cpu(lmbs[i].drc_index)); | ||
477 | } | ||
478 | rc = -EINVAL; | ||
479 | } else { | ||
480 | for (i = 0; i < num_lmbs; i++) { | ||
481 | if (!lmbs[i].reserved) | ||
482 | continue; | ||
483 | |||
484 | pr_info("Memory at %llx (drc index %x) was hot-added\n", | ||
485 | lmbs[i].base_addr, lmbs[i].drc_index); | ||
486 | lmbs[i].reserved = 0; | ||
487 | } | ||
488 | } | ||
489 | |||
490 | return rc; | ||
491 | } | ||
492 | |||
493 | static int dlpar_memory_add_by_index(u32 drc_index, struct property *prop) | ||
494 | { | ||
495 | struct of_drconf_cell *lmbs; | ||
496 | u32 num_lmbs, *p; | ||
497 | int i, lmb_found; | ||
498 | int rc; | ||
499 | |||
500 | pr_info("Attempting to hot-add LMB, drc index %x\n", drc_index); | ||
501 | |||
502 | p = prop->value; | ||
503 | num_lmbs = *p++; | ||
504 | lmbs = (struct of_drconf_cell *)p; | ||
505 | |||
506 | lmb_found = 0; | ||
507 | for (i = 0; i < num_lmbs; i++) { | ||
508 | if (lmbs[i].drc_index == drc_index) { | ||
509 | lmb_found = 1; | ||
510 | rc = dlpar_add_lmb(&lmbs[i]); | ||
511 | break; | ||
512 | } | ||
513 | } | ||
514 | |||
515 | if (!lmb_found) | ||
516 | rc = -EINVAL; | ||
517 | |||
518 | if (rc) | ||
519 | pr_info("Failed to hot-add memory, drc index %x\n", drc_index); | ||
520 | else | ||
521 | pr_info("Memory at %llx (drc index %x) was hot-added\n", | ||
522 | lmbs[i].base_addr, drc_index); | ||
523 | |||
524 | return rc; | ||
525 | } | ||
526 | |||
527 | static void dlpar_update_drconf_property(struct device_node *dn, | ||
528 | struct property *prop) | ||
529 | { | ||
530 | struct of_drconf_cell *lmbs; | ||
531 | u32 num_lmbs, *p; | ||
532 | int i; | ||
533 | |||
534 | /* Convert the property back to BE */ | ||
535 | p = prop->value; | ||
536 | num_lmbs = *p; | ||
537 | *p = cpu_to_be32(*p); | ||
538 | p++; | ||
539 | |||
540 | lmbs = (struct of_drconf_cell *)p; | ||
541 | for (i = 0; i < num_lmbs; i++) { | ||
542 | lmbs[i].base_addr = cpu_to_be64(lmbs[i].base_addr); | ||
543 | lmbs[i].drc_index = cpu_to_be32(lmbs[i].drc_index); | ||
544 | lmbs[i].flags = cpu_to_be32(lmbs[i].flags); | ||
545 | } | ||
546 | |||
547 | rtas_hp_event = true; | ||
548 | of_update_property(dn, prop); | ||
549 | rtas_hp_event = false; | ||
550 | } | ||
551 | |||
552 | int dlpar_memory(struct pseries_hp_errorlog *hp_elog) | ||
553 | { | ||
554 | struct device_node *dn; | ||
555 | struct property *prop; | ||
556 | u32 count, drc_index; | ||
557 | int rc; | ||
558 | |||
559 | count = hp_elog->_drc_u.drc_count; | ||
560 | drc_index = hp_elog->_drc_u.drc_index; | ||
561 | |||
562 | lock_device_hotplug(); | ||
563 | |||
564 | dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory"); | ||
565 | if (!dn) | ||
566 | return -EINVAL; | ||
567 | |||
568 | prop = dlpar_clone_drconf_property(dn); | ||
569 | if (!prop) { | ||
570 | of_node_put(dn); | ||
571 | return -EINVAL; | ||
572 | } | ||
573 | |||
574 | switch (hp_elog->action) { | ||
575 | case PSERIES_HP_ELOG_ACTION_ADD: | ||
576 | if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) | ||
577 | rc = dlpar_memory_add_by_count(count, prop); | ||
578 | else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) | ||
579 | rc = dlpar_memory_add_by_index(drc_index, prop); | ||
580 | else | ||
581 | rc = -EINVAL; | ||
582 | break; | ||
583 | case PSERIES_HP_ELOG_ACTION_REMOVE: | ||
584 | if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) | ||
585 | rc = dlpar_memory_remove_by_count(count, prop); | ||
586 | else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) | ||
587 | rc = dlpar_memory_remove_by_index(drc_index, prop); | ||
588 | else | ||
589 | rc = -EINVAL; | ||
590 | break; | ||
591 | default: | ||
592 | pr_err("Invalid action (%d) specified\n", hp_elog->action); | ||
593 | rc = -EINVAL; | ||
594 | break; | ||
595 | } | ||
596 | |||
597 | if (rc) | ||
598 | dlpar_free_drconf_property(prop); | ||
599 | else | ||
600 | dlpar_update_drconf_property(dn, prop); | ||
601 | |||
602 | of_node_put(dn); | ||
603 | unlock_device_hotplug(); | ||
604 | return rc; | ||
605 | } | ||
606 | |||
137 | static int pseries_add_mem_node(struct device_node *np) | 607 | static int pseries_add_mem_node(struct device_node *np) |
138 | { | 608 | { |
139 | const char *type; | 609 | const char *type; |
@@ -174,6 +644,9 @@ static int pseries_update_drconf_memory(struct of_reconfig_data *pr) | |||
174 | __be32 *p; | 644 | __be32 *p; |
175 | int i, rc = -EINVAL; | 645 | int i, rc = -EINVAL; |
176 | 646 | ||
647 | if (rtas_hp_event) | ||
648 | return 0; | ||
649 | |||
177 | memblock_size = pseries_memory_block_size(); | 650 | memblock_size = pseries_memory_block_size(); |
178 | if (!memblock_size) | 651 | if (!memblock_size) |
179 | return -EINVAL; | 652 | return -EINVAL; |
diff --git a/arch/powerpc/platforms/pseries/pseries.h b/arch/powerpc/platforms/pseries/pseries.h index cd64672e24f8..8411c27293e4 100644 --- a/arch/powerpc/platforms/pseries/pseries.h +++ b/arch/powerpc/platforms/pseries/pseries.h | |||
@@ -11,6 +11,7 @@ | |||
11 | #define _PSERIES_PSERIES_H | 11 | #define _PSERIES_PSERIES_H |
12 | 12 | ||
13 | #include <linux/interrupt.h> | 13 | #include <linux/interrupt.h> |
14 | #include <asm/rtas.h> | ||
14 | 15 | ||
15 | struct device_node; | 16 | struct device_node; |
16 | 17 | ||
@@ -60,6 +61,17 @@ extern struct device_node *dlpar_configure_connector(__be32, | |||
60 | struct device_node *); | 61 | struct device_node *); |
61 | extern int dlpar_attach_node(struct device_node *); | 62 | extern int dlpar_attach_node(struct device_node *); |
62 | extern int dlpar_detach_node(struct device_node *); | 63 | extern int dlpar_detach_node(struct device_node *); |
64 | extern int dlpar_acquire_drc(u32 drc_index); | ||
65 | extern int dlpar_release_drc(u32 drc_index); | ||
66 | |||
67 | #ifdef CONFIG_MEMORY_HOTPLUG | ||
68 | int dlpar_memory(struct pseries_hp_errorlog *hp_elog); | ||
69 | #else | ||
70 | static inline int dlpar_memory(struct pseries_hp_errorlog *hp_elog) | ||
71 | { | ||
72 | return -EOPNOTSUPP; | ||
73 | } | ||
74 | #endif | ||
63 | 75 | ||
64 | /* PCI root bridge prepare function override for pseries */ | 76 | /* PCI root bridge prepare function override for pseries */ |
65 | struct pci_host_bridge; | 77 | struct pci_host_bridge; |