Skip to content

Commit 5f97b2a

Browse files
nfontozbenh
authored andcommitted
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 <[email protected]> Signed-off-by: Benjamin Herrenschmidt <[email protected]>
1 parent 999e2da commit 5f97b2a

File tree

1 file changed

+265
-1
lines changed

1 file changed

+265
-1
lines changed

arch/powerpc/platforms/pseries/hotplug-memory.c

Lines changed: 265 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
#include <linux/memblock.h>
1717
#include <linux/memory.h>
1818
#include <linux/memory_hotplug.h>
19+
#include <linux/slab.h>
1920

2021
#include <asm/firmware.h>
2122
#include <asm/machdep.h>
2223
#include <asm/prom.h>
2324
#include <asm/sparsemem.h>
2425
#include "pseries.h"
2526

27+
static bool rtas_hp_event;
28+
2629
unsigned long pseries_memory_block_size(void)
2730
{
2831
struct device_node *np;
@@ -66,6 +69,67 @@ unsigned long pseries_memory_block_size(void)
6669
return memblock_size;
6770
}
6871

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+
69133
#ifdef CONFIG_MEMORY_HOTREMOVE
70134
static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size)
71135
{
@@ -136,19 +200,216 @@ static inline int pseries_remove_mem_node(struct device_node *np)
136200
}
137201
#endif /* CONFIG_MEMORY_HOTREMOVE */
138202

203+
static 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+
255+
static 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+
311+
static 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+
345+
static 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+
139370
int dlpar_memory(struct pseries_hp_errorlog *hp_elog)
140371
{
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;
142379

143380
lock_device_hotplug();
144381

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+
145392
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;
146401
default:
147402
pr_err("Invalid action (%d) specified\n", hp_elog->action);
148403
rc = -EINVAL;
149404
break;
150405
}
151406

407+
if (rc)
408+
dlpar_free_drconf_property(prop);
409+
else
410+
dlpar_update_drconf_property(dn, prop);
411+
412+
of_node_put(dn);
152413
unlock_device_hotplug();
153414
return rc;
154415
}
@@ -193,6 +454,9 @@ static int pseries_update_drconf_memory(struct of_reconfig_data *pr)
193454
__be32 *p;
194455
int i, rc = -EINVAL;
195456

457+
if (rtas_hp_event)
458+
return 0;
459+
196460
memblock_size = pseries_memory_block_size();
197461
if (!memblock_size)
198462
return -EINVAL;

0 commit comments

Comments
 (0)