aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/pseries
diff options
context:
space:
mode:
authorNathan Fontenot <nfont@linux.vnet.ibm.com>2015-02-10 14:48:25 -0500
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2015-03-16 20:03:01 -0400
commit5f97b2a0d176a94815ee1d3a0511d91a5575bf4a (patch)
tree98167fb8ddc8dc35df834b5e7a28c738a130a34a /arch/powerpc/platforms/pseries
parent999e2dadb6058568b8bcffec44da2a07952d84fe (diff)
powerpc/pseries: Implement memory hotplug add in the kernel
This patch adds the ability to do memory hotplug add in the kernel. Currently the operation to hotplug add memory is handled by the drmgr command which performs the operation by performing some work in user-space and making requests to the kernel to handle other pieces. By moving all of the work to the kernel we can do the add faster, and provide a common code path to do memory hotplug for both the PowerVM and PowerKVM environments. Signed-off-by: Nathan Fontenot <nfont@linux.vnet.ibm.com> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms/pseries')
-rw-r--r--arch/powerpc/platforms/pseries/hotplug-memory.c266
1 files changed, 265 insertions, 1 deletions
diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c
index 211d0bf7f5d9..f5eec0fc46df 100644
--- a/arch/powerpc/platforms/pseries/hotplug-memory.c
+++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
@@ -16,6 +16,7 @@
16#include <linux/memblock.h> 16#include <linux/memblock.h>
17#include <linux/memory.h> 17#include <linux/memory.h>
18#include <linux/memory_hotplug.h> 18#include <linux/memory_hotplug.h>
19#include <linux/slab.h>
19 20
20#include <asm/firmware.h> 21#include <asm/firmware.h>
21#include <asm/machdep.h> 22#include <asm/machdep.h>
@@ -23,6 +24,8 @@
23#include <asm/sparsemem.h> 24#include <asm/sparsemem.h>
24#include "pseries.h" 25#include "pseries.h"
25 26
27static bool rtas_hp_event;
28
26unsigned long pseries_memory_block_size(void) 29unsigned long pseries_memory_block_size(void)
27{ 30{
28 struct device_node *np; 31 struct device_node *np;
@@ -66,6 +69,67 @@ unsigned long pseries_memory_block_size(void)
66 return memblock_size; 69 return memblock_size;
67} 70}
68 71
72static void dlpar_free_drconf_property(struct property *prop)
73{
74 kfree(prop->name);
75 kfree(prop->value);
76 kfree(prop);
77}
78
79static 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
120static 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
69#ifdef CONFIG_MEMORY_HOTREMOVE 133#ifdef CONFIG_MEMORY_HOTREMOVE
70static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) 134static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size)
71{ 135{
@@ -136,19 +200,216 @@ static inline int pseries_remove_mem_node(struct device_node *np)
136} 200}
137#endif /* CONFIG_MEMORY_HOTREMOVE */ 201#endif /* CONFIG_MEMORY_HOTREMOVE */
138 202
203static int dlpar_add_lmb(struct of_drconf_cell *lmb)
204{
205 struct memory_block *mem_block;
206 unsigned long block_sz;
207 int nid, rc;
208
209 if (lmb->flags & DRCONF_MEM_ASSIGNED)
210 return -EINVAL;
211
212 block_sz = memory_block_size_bytes();
213
214 rc = dlpar_acquire_drc(lmb->drc_index);
215 if (rc)
216 return rc;
217
218 /* Find the node id for this address */
219 nid = memory_add_physaddr_to_nid(lmb->base_addr);
220
221 /* Add the memory */
222 rc = add_memory(nid, lmb->base_addr, block_sz);
223 if (rc) {
224 dlpar_release_drc(lmb->drc_index);
225 return rc;
226 }
227
228 /* Register this block of memory */
229 rc = memblock_add(lmb->base_addr, block_sz);
230 if (rc) {
231 remove_memory(nid, lmb->base_addr, block_sz);
232 dlpar_release_drc(lmb->drc_index);
233 return rc;
234 }
235
236 mem_block = lmb_to_memblock(lmb);
237 if (!mem_block) {
238 remove_memory(nid, lmb->base_addr, block_sz);
239 dlpar_release_drc(lmb->drc_index);
240 return -EINVAL;
241 }
242
243 rc = device_online(&mem_block->dev);
244 put_device(&mem_block->dev);
245 if (rc) {
246 remove_memory(nid, lmb->base_addr, block_sz);
247 dlpar_release_drc(lmb->drc_index);
248 return rc;
249 }
250
251 lmb->flags |= DRCONF_MEM_ASSIGNED;
252 return 0;
253}
254
255static int dlpar_memory_add_by_count(u32 lmbs_to_add, struct property *prop)
256{
257 struct of_drconf_cell *lmbs;
258 u32 num_lmbs, *p;
259 int lmbs_available = 0;
260 int lmbs_added = 0;
261 int i, rc;
262
263 pr_info("Attempting to hot-add %d LMB(s)\n", lmbs_to_add);
264
265 if (lmbs_to_add == 0)
266 return -EINVAL;
267
268 p = prop->value;
269 num_lmbs = *p++;
270 lmbs = (struct of_drconf_cell *)p;
271
272 /* Validate that there are enough LMBs to satisfy the request */
273 for (i = 0; i < num_lmbs; i++) {
274 if (!(lmbs[i].flags & DRCONF_MEM_ASSIGNED))
275 lmbs_available++;
276 }
277
278 if (lmbs_available < lmbs_to_add)
279 return -EINVAL;
280
281 for (i = 0; i < num_lmbs && lmbs_to_add != lmbs_added; i++) {
282 rc = dlpar_add_lmb(&lmbs[i]);
283 if (rc)
284 continue;
285
286 lmbs_added++;
287
288 /* Mark this lmb so we can remove it later if all of the
289 * requested LMBs cannot be added.
290 */
291 lmbs[i].reserved = 1;
292 }
293
294 if (lmbs_added != lmbs_to_add) {
295 /* TODO: remove added lmbs */
296 rc = -EINVAL;
297 } else {
298 for (i = 0; i < num_lmbs; i++) {
299 if (!lmbs[i].reserved)
300 continue;
301
302 pr_info("Memory at %llx (drc index %x) was hot-added\n",
303 lmbs[i].base_addr, lmbs[i].drc_index);
304 lmbs[i].reserved = 0;
305 }
306 }
307
308 return rc;
309}
310
311static int dlpar_memory_add_by_index(u32 drc_index, struct property *prop)
312{
313 struct of_drconf_cell *lmbs;
314 u32 num_lmbs, *p;
315 int i, lmb_found;
316 int rc;
317
318 pr_info("Attempting to hot-add LMB, drc index %x\n", drc_index);
319
320 p = prop->value;
321 num_lmbs = *p++;
322 lmbs = (struct of_drconf_cell *)p;
323
324 lmb_found = 0;
325 for (i = 0; i < num_lmbs; i++) {
326 if (lmbs[i].drc_index == drc_index) {
327 lmb_found = 1;
328 rc = dlpar_add_lmb(&lmbs[i]);
329 break;
330 }
331 }
332
333 if (!lmb_found)
334 rc = -EINVAL;
335
336 if (rc)
337 pr_info("Failed to hot-add memory, drc index %x\n", drc_index);
338 else
339 pr_info("Memory at %llx (drc index %x) was hot-added\n",
340 lmbs[i].base_addr, drc_index);
341
342 return rc;
343}
344
345static void dlpar_update_drconf_property(struct device_node *dn,
346 struct property *prop)
347{
348 struct of_drconf_cell *lmbs;
349 u32 num_lmbs, *p;
350 int i;
351
352 /* Convert the property back to BE */
353 p = prop->value;
354 num_lmbs = *p;
355 *p = cpu_to_be32(*p);
356 p++;
357
358 lmbs = (struct of_drconf_cell *)p;
359 for (i = 0; i < num_lmbs; i++) {
360 lmbs[i].base_addr = cpu_to_be64(lmbs[i].base_addr);
361 lmbs[i].drc_index = cpu_to_be32(lmbs[i].drc_index);
362 lmbs[i].flags = cpu_to_be32(lmbs[i].flags);
363 }
364
365 rtas_hp_event = true;
366 of_update_property(dn, prop);
367 rtas_hp_event = false;
368}
369
139int dlpar_memory(struct pseries_hp_errorlog *hp_elog) 370int dlpar_memory(struct pseries_hp_errorlog *hp_elog)
140{ 371{
141 int rc = 0; 372 struct device_node *dn;
373 struct property *prop;
374 u32 count, drc_index;
375 int rc;
376
377 count = hp_elog->_drc_u.drc_count;
378 drc_index = hp_elog->_drc_u.drc_index;
142 379
143 lock_device_hotplug(); 380 lock_device_hotplug();
144 381
382 dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
383 if (!dn)
384 return -EINVAL;
385
386 prop = dlpar_clone_drconf_property(dn);
387 if (!prop) {
388 of_node_put(dn);
389 return -EINVAL;
390 }
391
145 switch (hp_elog->action) { 392 switch (hp_elog->action) {
393 case PSERIES_HP_ELOG_ACTION_ADD:
394 if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT)
395 rc = dlpar_memory_add_by_count(count, prop);
396 else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX)
397 rc = dlpar_memory_add_by_index(drc_index, prop);
398 else
399 rc = -EINVAL;
400 break;
146 default: 401 default:
147 pr_err("Invalid action (%d) specified\n", hp_elog->action); 402 pr_err("Invalid action (%d) specified\n", hp_elog->action);
148 rc = -EINVAL; 403 rc = -EINVAL;
149 break; 404 break;
150 } 405 }
151 406
407 if (rc)
408 dlpar_free_drconf_property(prop);
409 else
410 dlpar_update_drconf_property(dn, prop);
411
412 of_node_put(dn);
152 unlock_device_hotplug(); 413 unlock_device_hotplug();
153 return rc; 414 return rc;
154} 415}
@@ -193,6 +454,9 @@ static int pseries_update_drconf_memory(struct of_reconfig_data *pr)
193 __be32 *p; 454 __be32 *p;
194 int i, rc = -EINVAL; 455 int i, rc = -EINVAL;
195 456
457 if (rtas_hp_event)
458 return 0;
459
196 memblock_size = pseries_memory_block_size(); 460 memblock_size = pseries_memory_block_size();
197 if (!memblock_size) 461 if (!memblock_size)
198 return -EINVAL; 462 return -EINVAL;