1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * pseries Memory Hotplug infrastructure. |
4 | * |
5 | * Copyright (C) 2008 Badari Pulavarty, IBM Corporation |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) "pseries-hotplug-mem: " fmt |
9 | |
10 | #include <linux/of.h> |
11 | #include <linux/of_address.h> |
12 | #include <linux/memblock.h> |
13 | #include <linux/memory.h> |
14 | #include <linux/memory_hotplug.h> |
15 | #include <linux/slab.h> |
16 | |
17 | #include <asm/firmware.h> |
18 | #include <asm/machdep.h> |
19 | #include <asm/sparsemem.h> |
20 | #include <asm/fadump.h> |
21 | #include <asm/drmem.h> |
22 | #include "pseries.h" |
23 | |
24 | static void dlpar_free_property(struct property *prop) |
25 | { |
26 | kfree(objp: prop->name); |
27 | kfree(objp: prop->value); |
28 | kfree(objp: prop); |
29 | } |
30 | |
31 | static struct property *dlpar_clone_property(struct property *prop, |
32 | u32 prop_size) |
33 | { |
34 | struct property *new_prop; |
35 | |
36 | new_prop = kzalloc(size: sizeof(*new_prop), GFP_KERNEL); |
37 | if (!new_prop) |
38 | return NULL; |
39 | |
40 | new_prop->name = kstrdup(s: prop->name, GFP_KERNEL); |
41 | new_prop->value = kzalloc(size: prop_size, GFP_KERNEL); |
42 | if (!new_prop->name || !new_prop->value) { |
43 | dlpar_free_property(prop: new_prop); |
44 | return NULL; |
45 | } |
46 | |
47 | memcpy(new_prop->value, prop->value, prop->length); |
48 | new_prop->length = prop_size; |
49 | |
50 | of_property_set_flag(p: new_prop, OF_DYNAMIC); |
51 | return new_prop; |
52 | } |
53 | |
54 | static bool find_aa_index(struct device_node *dr_node, |
55 | struct property *ala_prop, |
56 | const u32 *lmb_assoc, u32 *aa_index) |
57 | { |
58 | __be32 *assoc_arrays; |
59 | u32 new_prop_size; |
60 | struct property *new_prop; |
61 | int aa_arrays, aa_array_entries, aa_array_sz; |
62 | int i, index; |
63 | |
64 | /* |
65 | * The ibm,associativity-lookup-arrays property is defined to be |
66 | * a 32-bit value specifying the number of associativity arrays |
67 | * followed by a 32-bitvalue specifying the number of entries per |
68 | * array, followed by the associativity arrays. |
69 | */ |
70 | assoc_arrays = ala_prop->value; |
71 | |
72 | aa_arrays = be32_to_cpu(assoc_arrays[0]); |
73 | aa_array_entries = be32_to_cpu(assoc_arrays[1]); |
74 | aa_array_sz = aa_array_entries * sizeof(u32); |
75 | |
76 | for (i = 0; i < aa_arrays; i++) { |
77 | index = (i * aa_array_entries) + 2; |
78 | |
79 | if (memcmp(p: &assoc_arrays[index], q: &lmb_assoc[1], size: aa_array_sz)) |
80 | continue; |
81 | |
82 | *aa_index = i; |
83 | return true; |
84 | } |
85 | |
86 | new_prop_size = ala_prop->length + aa_array_sz; |
87 | new_prop = dlpar_clone_property(prop: ala_prop, prop_size: new_prop_size); |
88 | if (!new_prop) |
89 | return false; |
90 | |
91 | assoc_arrays = new_prop->value; |
92 | |
93 | /* increment the number of entries in the lookup array */ |
94 | assoc_arrays[0] = cpu_to_be32(aa_arrays + 1); |
95 | |
96 | /* copy the new associativity into the lookup array */ |
97 | index = aa_arrays * aa_array_entries + 2; |
98 | memcpy(&assoc_arrays[index], &lmb_assoc[1], aa_array_sz); |
99 | |
100 | of_update_property(np: dr_node, newprop: new_prop); |
101 | |
102 | /* |
103 | * The associativity lookup array index for this lmb is |
104 | * number of entries - 1 since we added its associativity |
105 | * to the end of the lookup array. |
106 | */ |
107 | *aa_index = be32_to_cpu(assoc_arrays[0]) - 1; |
108 | return true; |
109 | } |
110 | |
111 | static int update_lmb_associativity_index(struct drmem_lmb *lmb) |
112 | { |
113 | struct device_node *parent, *lmb_node, *dr_node; |
114 | struct property *ala_prop; |
115 | const u32 *lmb_assoc; |
116 | u32 aa_index; |
117 | bool found; |
118 | |
119 | parent = of_find_node_by_path(path: "/" ); |
120 | if (!parent) |
121 | return -ENODEV; |
122 | |
123 | lmb_node = dlpar_configure_connector(cpu_to_be32(lmb->drc_index), |
124 | parent); |
125 | of_node_put(node: parent); |
126 | if (!lmb_node) |
127 | return -EINVAL; |
128 | |
129 | lmb_assoc = of_get_property(node: lmb_node, name: "ibm,associativity" , NULL); |
130 | if (!lmb_assoc) { |
131 | dlpar_free_cc_nodes(lmb_node); |
132 | return -ENODEV; |
133 | } |
134 | |
135 | update_numa_distance(lmb_node); |
136 | |
137 | dr_node = of_find_node_by_path(path: "/ibm,dynamic-reconfiguration-memory" ); |
138 | if (!dr_node) { |
139 | dlpar_free_cc_nodes(lmb_node); |
140 | return -ENODEV; |
141 | } |
142 | |
143 | ala_prop = of_find_property(np: dr_node, name: "ibm,associativity-lookup-arrays" , |
144 | NULL); |
145 | if (!ala_prop) { |
146 | of_node_put(node: dr_node); |
147 | dlpar_free_cc_nodes(lmb_node); |
148 | return -ENODEV; |
149 | } |
150 | |
151 | found = find_aa_index(dr_node, ala_prop, lmb_assoc, aa_index: &aa_index); |
152 | |
153 | of_node_put(node: dr_node); |
154 | dlpar_free_cc_nodes(lmb_node); |
155 | |
156 | if (!found) { |
157 | pr_err("Could not find LMB associativity\n" ); |
158 | return -1; |
159 | } |
160 | |
161 | lmb->aa_index = aa_index; |
162 | return 0; |
163 | } |
164 | |
165 | static struct memory_block *lmb_to_memblock(struct drmem_lmb *lmb) |
166 | { |
167 | unsigned long section_nr; |
168 | struct memory_block *mem_block; |
169 | |
170 | section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr)); |
171 | |
172 | mem_block = find_memory_block(section_nr); |
173 | return mem_block; |
174 | } |
175 | |
176 | static int get_lmb_range(u32 drc_index, int n_lmbs, |
177 | struct drmem_lmb **start_lmb, |
178 | struct drmem_lmb **end_lmb) |
179 | { |
180 | struct drmem_lmb *lmb, *start, *end; |
181 | struct drmem_lmb *limit; |
182 | |
183 | start = NULL; |
184 | for_each_drmem_lmb(lmb) { |
185 | if (lmb->drc_index == drc_index) { |
186 | start = lmb; |
187 | break; |
188 | } |
189 | } |
190 | |
191 | if (!start) |
192 | return -EINVAL; |
193 | |
194 | end = &start[n_lmbs]; |
195 | |
196 | limit = &drmem_info->lmbs[drmem_info->n_lmbs]; |
197 | if (end > limit) |
198 | return -EINVAL; |
199 | |
200 | *start_lmb = start; |
201 | *end_lmb = end; |
202 | return 0; |
203 | } |
204 | |
205 | static int dlpar_change_lmb_state(struct drmem_lmb *lmb, bool online) |
206 | { |
207 | struct memory_block *mem_block; |
208 | int rc; |
209 | |
210 | mem_block = lmb_to_memblock(lmb); |
211 | if (!mem_block) { |
212 | pr_err("Failed memory block lookup for LMB 0x%x\n" , lmb->drc_index); |
213 | return -EINVAL; |
214 | } |
215 | |
216 | if (online && mem_block->dev.offline) |
217 | rc = device_online(dev: &mem_block->dev); |
218 | else if (!online && !mem_block->dev.offline) |
219 | rc = device_offline(dev: &mem_block->dev); |
220 | else |
221 | rc = 0; |
222 | |
223 | put_device(dev: &mem_block->dev); |
224 | |
225 | return rc; |
226 | } |
227 | |
228 | static int dlpar_online_lmb(struct drmem_lmb *lmb) |
229 | { |
230 | return dlpar_change_lmb_state(lmb, online: true); |
231 | } |
232 | |
233 | #ifdef CONFIG_MEMORY_HOTREMOVE |
234 | static int dlpar_offline_lmb(struct drmem_lmb *lmb) |
235 | { |
236 | return dlpar_change_lmb_state(lmb, online: false); |
237 | } |
238 | |
239 | static int pseries_remove_memblock(unsigned long base, unsigned long memblock_size) |
240 | { |
241 | unsigned long start_pfn; |
242 | int sections_per_block; |
243 | int i; |
244 | |
245 | start_pfn = base >> PAGE_SHIFT; |
246 | |
247 | lock_device_hotplug(); |
248 | |
249 | if (!pfn_valid(pfn: start_pfn)) |
250 | goto out; |
251 | |
252 | sections_per_block = memory_block_size / MIN_MEMORY_BLOCK_SIZE; |
253 | |
254 | for (i = 0; i < sections_per_block; i++) { |
255 | __remove_memory(start: base, MIN_MEMORY_BLOCK_SIZE); |
256 | base += MIN_MEMORY_BLOCK_SIZE; |
257 | } |
258 | |
259 | out: |
260 | /* Update memory regions for memory remove */ |
261 | memblock_remove(base, size: memblock_size); |
262 | unlock_device_hotplug(); |
263 | return 0; |
264 | } |
265 | |
266 | static int pseries_remove_mem_node(struct device_node *np) |
267 | { |
268 | int ret; |
269 | struct resource res; |
270 | |
271 | /* |
272 | * Check to see if we are actually removing memory |
273 | */ |
274 | if (!of_node_is_type(np, type: "memory" )) |
275 | return 0; |
276 | |
277 | /* |
278 | * Find the base address and size of the memblock |
279 | */ |
280 | ret = of_address_to_resource(dev: np, index: 0, r: &res); |
281 | if (ret) |
282 | return ret; |
283 | |
284 | pseries_remove_memblock(base: res.start, memblock_size: resource_size(res: &res)); |
285 | return 0; |
286 | } |
287 | |
288 | static bool lmb_is_removable(struct drmem_lmb *lmb) |
289 | { |
290 | if ((lmb->flags & DRCONF_MEM_RESERVED) || |
291 | !(lmb->flags & DRCONF_MEM_ASSIGNED)) |
292 | return false; |
293 | |
294 | #ifdef CONFIG_FA_DUMP |
295 | /* |
296 | * Don't hot-remove memory that falls in fadump boot memory area |
297 | * and memory that is reserved for capturing old kernel memory. |
298 | */ |
299 | if (is_fadump_memory_area(lmb->base_addr, memory_block_size_bytes())) |
300 | return false; |
301 | #endif |
302 | /* device_offline() will determine if we can actually remove this lmb */ |
303 | return true; |
304 | } |
305 | |
306 | static int dlpar_add_lmb(struct drmem_lmb *); |
307 | |
308 | static int dlpar_remove_lmb(struct drmem_lmb *lmb) |
309 | { |
310 | struct memory_block *mem_block; |
311 | int rc; |
312 | |
313 | if (!lmb_is_removable(lmb)) |
314 | return -EINVAL; |
315 | |
316 | mem_block = lmb_to_memblock(lmb); |
317 | if (mem_block == NULL) |
318 | return -EINVAL; |
319 | |
320 | rc = dlpar_offline_lmb(lmb); |
321 | if (rc) { |
322 | put_device(dev: &mem_block->dev); |
323 | return rc; |
324 | } |
325 | |
326 | __remove_memory(start: lmb->base_addr, size: memory_block_size); |
327 | put_device(dev: &mem_block->dev); |
328 | |
329 | /* Update memory regions for memory remove */ |
330 | memblock_remove(base: lmb->base_addr, size: memory_block_size); |
331 | |
332 | invalidate_lmb_associativity_index(lmb); |
333 | lmb->flags &= ~DRCONF_MEM_ASSIGNED; |
334 | |
335 | return 0; |
336 | } |
337 | |
338 | static int dlpar_memory_remove_by_count(u32 lmbs_to_remove) |
339 | { |
340 | struct drmem_lmb *lmb; |
341 | int lmbs_reserved = 0; |
342 | int lmbs_available = 0; |
343 | int rc; |
344 | |
345 | pr_info("Attempting to hot-remove %d LMB(s)\n" , lmbs_to_remove); |
346 | |
347 | if (lmbs_to_remove == 0) |
348 | return -EINVAL; |
349 | |
350 | /* Validate that there are enough LMBs to satisfy the request */ |
351 | for_each_drmem_lmb(lmb) { |
352 | if (lmb_is_removable(lmb)) |
353 | lmbs_available++; |
354 | |
355 | if (lmbs_available == lmbs_to_remove) |
356 | break; |
357 | } |
358 | |
359 | if (lmbs_available < lmbs_to_remove) { |
360 | pr_info("Not enough LMBs available (%d of %d) to satisfy request\n" , |
361 | lmbs_available, lmbs_to_remove); |
362 | return -EINVAL; |
363 | } |
364 | |
365 | for_each_drmem_lmb(lmb) { |
366 | rc = dlpar_remove_lmb(lmb); |
367 | if (rc) |
368 | continue; |
369 | |
370 | /* Mark this lmb so we can add it later if all of the |
371 | * requested LMBs cannot be removed. |
372 | */ |
373 | drmem_mark_lmb_reserved(lmb); |
374 | |
375 | lmbs_reserved++; |
376 | if (lmbs_reserved == lmbs_to_remove) |
377 | break; |
378 | } |
379 | |
380 | if (lmbs_reserved != lmbs_to_remove) { |
381 | pr_err("Memory hot-remove failed, adding LMB's back\n" ); |
382 | |
383 | for_each_drmem_lmb(lmb) { |
384 | if (!drmem_lmb_reserved(lmb)) |
385 | continue; |
386 | |
387 | rc = dlpar_add_lmb(lmb); |
388 | if (rc) |
389 | pr_err("Failed to add LMB back, drc index %x\n" , |
390 | lmb->drc_index); |
391 | |
392 | drmem_remove_lmb_reservation(lmb); |
393 | |
394 | lmbs_reserved--; |
395 | if (lmbs_reserved == 0) |
396 | break; |
397 | } |
398 | |
399 | rc = -EINVAL; |
400 | } else { |
401 | for_each_drmem_lmb(lmb) { |
402 | if (!drmem_lmb_reserved(lmb)) |
403 | continue; |
404 | |
405 | dlpar_release_drc(drc_index: lmb->drc_index); |
406 | pr_info("Memory at %llx was hot-removed\n" , |
407 | lmb->base_addr); |
408 | |
409 | drmem_remove_lmb_reservation(lmb); |
410 | |
411 | lmbs_reserved--; |
412 | if (lmbs_reserved == 0) |
413 | break; |
414 | } |
415 | rc = 0; |
416 | } |
417 | |
418 | return rc; |
419 | } |
420 | |
421 | static int dlpar_memory_remove_by_index(u32 drc_index) |
422 | { |
423 | struct drmem_lmb *lmb; |
424 | int lmb_found; |
425 | int rc; |
426 | |
427 | pr_debug("Attempting to hot-remove LMB, drc index %x\n" , drc_index); |
428 | |
429 | lmb_found = 0; |
430 | for_each_drmem_lmb(lmb) { |
431 | if (lmb->drc_index == drc_index) { |
432 | lmb_found = 1; |
433 | rc = dlpar_remove_lmb(lmb); |
434 | if (!rc) |
435 | dlpar_release_drc(drc_index: lmb->drc_index); |
436 | |
437 | break; |
438 | } |
439 | } |
440 | |
441 | if (!lmb_found) { |
442 | pr_debug("Failed to look up LMB for drc index %x\n" , drc_index); |
443 | rc = -EINVAL; |
444 | } else if (rc) { |
445 | pr_debug("Failed to hot-remove memory at %llx\n" , |
446 | lmb->base_addr); |
447 | } else { |
448 | pr_debug("Memory at %llx was hot-removed\n" , lmb->base_addr); |
449 | } |
450 | |
451 | return rc; |
452 | } |
453 | |
454 | static int dlpar_memory_remove_by_ic(u32 lmbs_to_remove, u32 drc_index) |
455 | { |
456 | struct drmem_lmb *lmb, *start_lmb, *end_lmb; |
457 | int rc; |
458 | |
459 | pr_info("Attempting to hot-remove %u LMB(s) at %x\n" , |
460 | lmbs_to_remove, drc_index); |
461 | |
462 | if (lmbs_to_remove == 0) |
463 | return -EINVAL; |
464 | |
465 | rc = get_lmb_range(drc_index, n_lmbs: lmbs_to_remove, start_lmb: &start_lmb, end_lmb: &end_lmb); |
466 | if (rc) |
467 | return -EINVAL; |
468 | |
469 | /* |
470 | * Validate that all LMBs in range are not reserved. Note that it |
471 | * is ok if they are !ASSIGNED since our goal here is to remove the |
472 | * LMB range, regardless of whether some LMBs were already removed |
473 | * by any other reason. |
474 | * |
475 | * This is a contrast to what is done in remove_by_count() where we |
476 | * check for both RESERVED and !ASSIGNED (via lmb_is_removable()), |
477 | * because we want to remove a fixed amount of LMBs in that function. |
478 | */ |
479 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
480 | if (lmb->flags & DRCONF_MEM_RESERVED) { |
481 | pr_err("Memory at %llx (drc index %x) is reserved\n" , |
482 | lmb->base_addr, lmb->drc_index); |
483 | return -EINVAL; |
484 | } |
485 | } |
486 | |
487 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
488 | /* |
489 | * dlpar_remove_lmb() will error out if the LMB is already |
490 | * !ASSIGNED, but this case is a no-op for us. |
491 | */ |
492 | if (!(lmb->flags & DRCONF_MEM_ASSIGNED)) |
493 | continue; |
494 | |
495 | rc = dlpar_remove_lmb(lmb); |
496 | if (rc) |
497 | break; |
498 | |
499 | drmem_mark_lmb_reserved(lmb); |
500 | } |
501 | |
502 | if (rc) { |
503 | pr_err("Memory indexed-count-remove failed, adding any removed LMBs\n" ); |
504 | |
505 | |
506 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
507 | if (!drmem_lmb_reserved(lmb)) |
508 | continue; |
509 | |
510 | /* |
511 | * Setting the isolation state of an UNISOLATED/CONFIGURED |
512 | * device to UNISOLATE is a no-op, but the hypervisor can |
513 | * use it as a hint that the LMB removal failed. |
514 | */ |
515 | dlpar_unisolate_drc(drc_index: lmb->drc_index); |
516 | |
517 | rc = dlpar_add_lmb(lmb); |
518 | if (rc) |
519 | pr_err("Failed to add LMB, drc index %x\n" , |
520 | lmb->drc_index); |
521 | |
522 | drmem_remove_lmb_reservation(lmb); |
523 | } |
524 | rc = -EINVAL; |
525 | } else { |
526 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
527 | if (!drmem_lmb_reserved(lmb)) |
528 | continue; |
529 | |
530 | dlpar_release_drc(drc_index: lmb->drc_index); |
531 | pr_info("Memory at %llx (drc index %x) was hot-removed\n" , |
532 | lmb->base_addr, lmb->drc_index); |
533 | |
534 | drmem_remove_lmb_reservation(lmb); |
535 | } |
536 | } |
537 | |
538 | return rc; |
539 | } |
540 | |
541 | #else |
542 | static inline int pseries_remove_memblock(unsigned long base, |
543 | unsigned long memblock_size) |
544 | { |
545 | return -EOPNOTSUPP; |
546 | } |
547 | static inline int pseries_remove_mem_node(struct device_node *np) |
548 | { |
549 | return 0; |
550 | } |
551 | static int dlpar_remove_lmb(struct drmem_lmb *lmb) |
552 | { |
553 | return -EOPNOTSUPP; |
554 | } |
555 | static int dlpar_memory_remove_by_count(u32 lmbs_to_remove) |
556 | { |
557 | return -EOPNOTSUPP; |
558 | } |
559 | static int dlpar_memory_remove_by_index(u32 drc_index) |
560 | { |
561 | return -EOPNOTSUPP; |
562 | } |
563 | |
564 | static int dlpar_memory_remove_by_ic(u32 lmbs_to_remove, u32 drc_index) |
565 | { |
566 | return -EOPNOTSUPP; |
567 | } |
568 | #endif /* CONFIG_MEMORY_HOTREMOVE */ |
569 | |
570 | static int dlpar_add_lmb(struct drmem_lmb *lmb) |
571 | { |
572 | unsigned long block_sz; |
573 | int nid, rc; |
574 | |
575 | if (lmb->flags & DRCONF_MEM_ASSIGNED) |
576 | return -EINVAL; |
577 | |
578 | rc = update_lmb_associativity_index(lmb); |
579 | if (rc) { |
580 | dlpar_release_drc(drc_index: lmb->drc_index); |
581 | pr_err("Failed to configure LMB 0x%x\n" , lmb->drc_index); |
582 | return rc; |
583 | } |
584 | |
585 | block_sz = memory_block_size_bytes(); |
586 | |
587 | /* Find the node id for this LMB. Fake one if necessary. */ |
588 | nid = of_drconf_to_nid_single(lmb); |
589 | if (nid < 0 || !node_possible(nid)) |
590 | nid = first_online_node; |
591 | |
592 | /* Add the memory */ |
593 | rc = __add_memory(nid, start: lmb->base_addr, size: block_sz, MHP_MEMMAP_ON_MEMORY); |
594 | if (rc) { |
595 | pr_err("Failed to add LMB 0x%x to node %u" , lmb->drc_index, nid); |
596 | invalidate_lmb_associativity_index(lmb); |
597 | return rc; |
598 | } |
599 | |
600 | rc = dlpar_online_lmb(lmb); |
601 | if (rc) { |
602 | pr_err("Failed to online LMB 0x%x on node %u\n" , lmb->drc_index, nid); |
603 | __remove_memory(start: lmb->base_addr, size: block_sz); |
604 | invalidate_lmb_associativity_index(lmb); |
605 | } else { |
606 | lmb->flags |= DRCONF_MEM_ASSIGNED; |
607 | } |
608 | |
609 | return rc; |
610 | } |
611 | |
612 | static int dlpar_memory_add_by_count(u32 lmbs_to_add) |
613 | { |
614 | struct drmem_lmb *lmb; |
615 | int lmbs_available = 0; |
616 | int lmbs_reserved = 0; |
617 | int rc; |
618 | |
619 | pr_info("Attempting to hot-add %d LMB(s)\n" , lmbs_to_add); |
620 | |
621 | if (lmbs_to_add == 0) |
622 | return -EINVAL; |
623 | |
624 | /* Validate that there are enough LMBs to satisfy the request */ |
625 | for_each_drmem_lmb(lmb) { |
626 | if (lmb->flags & DRCONF_MEM_RESERVED) |
627 | continue; |
628 | |
629 | if (!(lmb->flags & DRCONF_MEM_ASSIGNED)) |
630 | lmbs_available++; |
631 | |
632 | if (lmbs_available == lmbs_to_add) |
633 | break; |
634 | } |
635 | |
636 | if (lmbs_available < lmbs_to_add) |
637 | return -EINVAL; |
638 | |
639 | for_each_drmem_lmb(lmb) { |
640 | if (lmb->flags & DRCONF_MEM_ASSIGNED) |
641 | continue; |
642 | |
643 | rc = dlpar_acquire_drc(drc_index: lmb->drc_index); |
644 | if (rc) |
645 | continue; |
646 | |
647 | rc = dlpar_add_lmb(lmb); |
648 | if (rc) { |
649 | dlpar_release_drc(drc_index: lmb->drc_index); |
650 | continue; |
651 | } |
652 | |
653 | /* Mark this lmb so we can remove it later if all of the |
654 | * requested LMBs cannot be added. |
655 | */ |
656 | drmem_mark_lmb_reserved(lmb); |
657 | lmbs_reserved++; |
658 | if (lmbs_reserved == lmbs_to_add) |
659 | break; |
660 | } |
661 | |
662 | if (lmbs_reserved != lmbs_to_add) { |
663 | pr_err("Memory hot-add failed, removing any added LMBs\n" ); |
664 | |
665 | for_each_drmem_lmb(lmb) { |
666 | if (!drmem_lmb_reserved(lmb)) |
667 | continue; |
668 | |
669 | rc = dlpar_remove_lmb(lmb); |
670 | if (rc) |
671 | pr_err("Failed to remove LMB, drc index %x\n" , |
672 | lmb->drc_index); |
673 | else |
674 | dlpar_release_drc(drc_index: lmb->drc_index); |
675 | |
676 | drmem_remove_lmb_reservation(lmb); |
677 | lmbs_reserved--; |
678 | |
679 | if (lmbs_reserved == 0) |
680 | break; |
681 | } |
682 | rc = -EINVAL; |
683 | } else { |
684 | for_each_drmem_lmb(lmb) { |
685 | if (!drmem_lmb_reserved(lmb)) |
686 | continue; |
687 | |
688 | pr_debug("Memory at %llx (drc index %x) was hot-added\n" , |
689 | lmb->base_addr, lmb->drc_index); |
690 | drmem_remove_lmb_reservation(lmb); |
691 | lmbs_reserved--; |
692 | |
693 | if (lmbs_reserved == 0) |
694 | break; |
695 | } |
696 | rc = 0; |
697 | } |
698 | |
699 | return rc; |
700 | } |
701 | |
702 | static int dlpar_memory_add_by_index(u32 drc_index) |
703 | { |
704 | struct drmem_lmb *lmb; |
705 | int rc, lmb_found; |
706 | |
707 | pr_info("Attempting to hot-add LMB, drc index %x\n" , drc_index); |
708 | |
709 | lmb_found = 0; |
710 | for_each_drmem_lmb(lmb) { |
711 | if (lmb->drc_index == drc_index) { |
712 | lmb_found = 1; |
713 | rc = dlpar_acquire_drc(drc_index: lmb->drc_index); |
714 | if (!rc) { |
715 | rc = dlpar_add_lmb(lmb); |
716 | if (rc) |
717 | dlpar_release_drc(drc_index: lmb->drc_index); |
718 | } |
719 | |
720 | break; |
721 | } |
722 | } |
723 | |
724 | if (!lmb_found) |
725 | rc = -EINVAL; |
726 | |
727 | if (rc) |
728 | pr_info("Failed to hot-add memory, drc index %x\n" , drc_index); |
729 | else |
730 | pr_info("Memory at %llx (drc index %x) was hot-added\n" , |
731 | lmb->base_addr, drc_index); |
732 | |
733 | return rc; |
734 | } |
735 | |
736 | static int dlpar_memory_add_by_ic(u32 lmbs_to_add, u32 drc_index) |
737 | { |
738 | struct drmem_lmb *lmb, *start_lmb, *end_lmb; |
739 | int rc; |
740 | |
741 | pr_info("Attempting to hot-add %u LMB(s) at index %x\n" , |
742 | lmbs_to_add, drc_index); |
743 | |
744 | if (lmbs_to_add == 0) |
745 | return -EINVAL; |
746 | |
747 | rc = get_lmb_range(drc_index, n_lmbs: lmbs_to_add, start_lmb: &start_lmb, end_lmb: &end_lmb); |
748 | if (rc) |
749 | return -EINVAL; |
750 | |
751 | /* Validate that the LMBs in this range are not reserved */ |
752 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
753 | /* Fail immediately if the whole range can't be hot-added */ |
754 | if (lmb->flags & DRCONF_MEM_RESERVED) { |
755 | pr_err("Memory at %llx (drc index %x) is reserved\n" , |
756 | lmb->base_addr, lmb->drc_index); |
757 | return -EINVAL; |
758 | } |
759 | } |
760 | |
761 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
762 | if (lmb->flags & DRCONF_MEM_ASSIGNED) |
763 | continue; |
764 | |
765 | rc = dlpar_acquire_drc(drc_index: lmb->drc_index); |
766 | if (rc) |
767 | break; |
768 | |
769 | rc = dlpar_add_lmb(lmb); |
770 | if (rc) { |
771 | dlpar_release_drc(drc_index: lmb->drc_index); |
772 | break; |
773 | } |
774 | |
775 | drmem_mark_lmb_reserved(lmb); |
776 | } |
777 | |
778 | if (rc) { |
779 | pr_err("Memory indexed-count-add failed, removing any added LMBs\n" ); |
780 | |
781 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
782 | if (!drmem_lmb_reserved(lmb)) |
783 | continue; |
784 | |
785 | rc = dlpar_remove_lmb(lmb); |
786 | if (rc) |
787 | pr_err("Failed to remove LMB, drc index %x\n" , |
788 | lmb->drc_index); |
789 | else |
790 | dlpar_release_drc(drc_index: lmb->drc_index); |
791 | |
792 | drmem_remove_lmb_reservation(lmb); |
793 | } |
794 | rc = -EINVAL; |
795 | } else { |
796 | for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) { |
797 | if (!drmem_lmb_reserved(lmb)) |
798 | continue; |
799 | |
800 | pr_info("Memory at %llx (drc index %x) was hot-added\n" , |
801 | lmb->base_addr, lmb->drc_index); |
802 | drmem_remove_lmb_reservation(lmb); |
803 | } |
804 | } |
805 | |
806 | return rc; |
807 | } |
808 | |
809 | int dlpar_memory(struct pseries_hp_errorlog *hp_elog) |
810 | { |
811 | u32 count, drc_index; |
812 | int rc; |
813 | |
814 | lock_device_hotplug(); |
815 | |
816 | switch (hp_elog->action) { |
817 | case PSERIES_HP_ELOG_ACTION_ADD: |
818 | switch (hp_elog->id_type) { |
819 | case PSERIES_HP_ELOG_ID_DRC_COUNT: |
820 | count = hp_elog->_drc_u.drc_count; |
821 | rc = dlpar_memory_add_by_count(lmbs_to_add: count); |
822 | break; |
823 | case PSERIES_HP_ELOG_ID_DRC_INDEX: |
824 | drc_index = hp_elog->_drc_u.drc_index; |
825 | rc = dlpar_memory_add_by_index(drc_index); |
826 | break; |
827 | case PSERIES_HP_ELOG_ID_DRC_IC: |
828 | count = hp_elog->_drc_u.ic.count; |
829 | drc_index = hp_elog->_drc_u.ic.index; |
830 | rc = dlpar_memory_add_by_ic(lmbs_to_add: count, drc_index); |
831 | break; |
832 | default: |
833 | rc = -EINVAL; |
834 | break; |
835 | } |
836 | |
837 | break; |
838 | case PSERIES_HP_ELOG_ACTION_REMOVE: |
839 | switch (hp_elog->id_type) { |
840 | case PSERIES_HP_ELOG_ID_DRC_COUNT: |
841 | count = hp_elog->_drc_u.drc_count; |
842 | rc = dlpar_memory_remove_by_count(lmbs_to_remove: count); |
843 | break; |
844 | case PSERIES_HP_ELOG_ID_DRC_INDEX: |
845 | drc_index = hp_elog->_drc_u.drc_index; |
846 | rc = dlpar_memory_remove_by_index(drc_index); |
847 | break; |
848 | case PSERIES_HP_ELOG_ID_DRC_IC: |
849 | count = hp_elog->_drc_u.ic.count; |
850 | drc_index = hp_elog->_drc_u.ic.index; |
851 | rc = dlpar_memory_remove_by_ic(lmbs_to_remove: count, drc_index); |
852 | break; |
853 | default: |
854 | rc = -EINVAL; |
855 | break; |
856 | } |
857 | |
858 | break; |
859 | default: |
860 | pr_err("Invalid action (%d) specified\n" , hp_elog->action); |
861 | rc = -EINVAL; |
862 | break; |
863 | } |
864 | |
865 | if (!rc) |
866 | rc = drmem_update_dt(); |
867 | |
868 | unlock_device_hotplug(); |
869 | return rc; |
870 | } |
871 | |
872 | static int pseries_add_mem_node(struct device_node *np) |
873 | { |
874 | int ret; |
875 | struct resource res; |
876 | |
877 | /* |
878 | * Check to see if we are actually adding memory |
879 | */ |
880 | if (!of_node_is_type(np, type: "memory" )) |
881 | return 0; |
882 | |
883 | /* |
884 | * Find the base and size of the memblock |
885 | */ |
886 | ret = of_address_to_resource(dev: np, index: 0, r: &res); |
887 | if (ret) |
888 | return ret; |
889 | |
890 | /* |
891 | * Update memory region to represent the memory add |
892 | */ |
893 | ret = memblock_add(base: res.start, size: resource_size(res: &res)); |
894 | return (ret < 0) ? -EINVAL : 0; |
895 | } |
896 | |
897 | static int pseries_memory_notifier(struct notifier_block *nb, |
898 | unsigned long action, void *data) |
899 | { |
900 | struct of_reconfig_data *rd = data; |
901 | int err = 0; |
902 | |
903 | switch (action) { |
904 | case OF_RECONFIG_ATTACH_NODE: |
905 | err = pseries_add_mem_node(np: rd->dn); |
906 | break; |
907 | case OF_RECONFIG_DETACH_NODE: |
908 | err = pseries_remove_mem_node(np: rd->dn); |
909 | break; |
910 | case OF_RECONFIG_UPDATE_PROPERTY: |
911 | if (!strcmp(rd->dn->name, |
912 | "ibm,dynamic-reconfiguration-memory" )) |
913 | drmem_update_lmbs(rd->prop); |
914 | } |
915 | return notifier_from_errno(err); |
916 | } |
917 | |
918 | static struct notifier_block pseries_mem_nb = { |
919 | .notifier_call = pseries_memory_notifier, |
920 | }; |
921 | |
922 | static int __init pseries_memory_hotplug_init(void) |
923 | { |
924 | if (firmware_has_feature(FW_FEATURE_LPAR)) |
925 | of_reconfig_notifier_register(&pseries_mem_nb); |
926 | |
927 | return 0; |
928 | } |
929 | machine_device_initcall(pseries, pseries_memory_hotplug_init); |
930 | |