diff options
Diffstat (limited to 'arch/powerpc/platforms/pseries/hotplug-memory.c')
| -rw-r--r-- | arch/powerpc/platforms/pseries/hotplug-memory.c | 216 |
1 files changed, 145 insertions, 71 deletions
diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c index bc880366414..7995135170a 100644 --- a/arch/powerpc/platforms/pseries/hotplug-memory.c +++ b/arch/powerpc/platforms/pseries/hotplug-memory.c @@ -10,51 +10,68 @@ */ #include <linux/of.h> +#include <linux/of_address.h> #include <linux/memblock.h> #include <linux/vmalloc.h> +#include <linux/memory.h> +#include <linux/memory_hotplug.h> + #include <asm/firmware.h> #include <asm/machdep.h> -#include <asm/pSeries_reconfig.h> +#include <asm/prom.h> #include <asm/sparsemem.h> -static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) +unsigned long pseries_memory_block_size(void) { - unsigned long start, start_pfn; - struct zone *zone; - int ret; + struct device_node *np; + unsigned int memblock_size = MIN_MEMORY_BLOCK_SIZE; + struct resource r; - start_pfn = base >> PAGE_SHIFT; + np = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory"); + if (np) { + const __be64 *size; - if (!pfn_valid(start_pfn)) { - memblock_remove(base, memblock_size); - return 0; - } + size = of_get_property(np, "ibm,lmb-size", NULL); + if (size) + memblock_size = be64_to_cpup(size); + of_node_put(np); + } else if (machine_is(pseries)) { + /* This fallback really only applies to pseries */ + unsigned int memzero_size = 0; - zone = page_zone(pfn_to_page(start_pfn)); + np = of_find_node_by_path("/memory@0"); + if (np) { + if (!of_address_to_resource(np, 0, &r)) + memzero_size = resource_size(&r); + of_node_put(np); + } - /* - * Remove section mappings and sysfs entries for the - * section of the memory we are removing. - * - * NOTE: Ideally, this should be done in generic code like - * remove_memory(). But remove_memory() gets called by writing - * to sysfs "state" file and we can't remove sysfs entries - * while writing to it. So we have to defer it to here. - */ - ret = __remove_pages(zone, start_pfn, memblock_size >> PAGE_SHIFT); - if (ret) - return ret; + if (memzero_size) { + /* We now know the size of memory@0, use this to find + * the first memoryblock and get its size. + */ + char buf[64]; - /* - * Update memory regions for memory remove - */ - memblock_remove(base, memblock_size); + sprintf(buf, "/memory@%x", memzero_size); + np = of_find_node_by_path(buf); + if (np) { + if (!of_address_to_resource(np, 0, &r)) + memblock_size = resource_size(&r); + of_node_put(np); + } + } + } + return memblock_size; +} - /* - * Remove htab bolted mappings for this section of memory - */ - start = (unsigned long)__va(base); - ret = remove_section_mapping(start, start + memblock_size); +#ifdef CONFIG_MEMORY_HOTREMOVE +static int pseries_remove_memory(u64 start, u64 size) +{ + int ret; + + /* Remove htab bolted mappings for this section of memory */ + start = (unsigned long)__va(start); + ret = remove_section_mapping(start, start + size); /* Ensure all vmalloc mappings are flushed in case they also * hit that section of memory @@ -64,7 +81,36 @@ static int pseries_remove_memblock(unsigned long base, unsigned int memblock_siz return ret; } -static int pseries_remove_memory(struct device_node *np) +static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) +{ + unsigned long block_sz, start_pfn; + int sections_per_block; + int i, nid; + + start_pfn = base >> PAGE_SHIFT; + + lock_device_hotplug(); + + if (!pfn_valid(start_pfn)) + goto out; + + block_sz = pseries_memory_block_size(); + sections_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE; + nid = memory_add_physaddr_to_nid(base); + + for (i = 0; i < sections_per_block; i++) { + remove_memory(nid, base, MIN_MEMORY_BLOCK_SIZE); + base += MIN_MEMORY_BLOCK_SIZE; + } + +out: + /* Update memory regions for memory remove */ + memblock_remove(base, memblock_size); + unlock_device_hotplug(); + return 0; +} + +static int pseries_remove_mem_node(struct device_node *np) { const char *type; const unsigned int *regs; @@ -89,11 +135,22 @@ static int pseries_remove_memory(struct device_node *np) base = *(unsigned long *)regs; lmb_size = regs[3]; - ret = pseries_remove_memblock(base, lmb_size); - return ret; + pseries_remove_memblock(base, lmb_size); + return 0; } +#else +static inline int pseries_remove_memblock(unsigned long base, + unsigned int memblock_size) +{ + return -EOPNOTSUPP; +} +static inline int pseries_remove_mem_node(struct device_node *np) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_MEMORY_HOTREMOVE */ -static int pseries_add_memory(struct device_node *np) +static int pseries_add_mem_node(struct device_node *np) { const char *type; const unsigned int *regs; @@ -125,59 +182,72 @@ static int pseries_add_memory(struct device_node *np) return (ret < 0) ? -EINVAL : 0; } -static int pseries_drconf_memory(unsigned long *base, unsigned int action) +static int pseries_update_drconf_memory(struct of_prop_reconfig *pr) { - struct device_node *np; - const unsigned long *lmb_size; - int rc; + struct of_drconf_cell *new_drmem, *old_drmem; + unsigned long memblock_size; + u32 entries; + u32 *p; + int i, rc = -EINVAL; - np = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory"); - if (!np) + memblock_size = pseries_memory_block_size(); + if (!memblock_size) return -EINVAL; - lmb_size = of_get_property(np, "ibm,lmb-size", NULL); - if (!lmb_size) { - of_node_put(np); + p = (u32 *)of_get_property(pr->dn, "ibm,dynamic-memory", NULL); + if (!p) return -EINVAL; - } - if (action == PSERIES_DRCONF_MEM_ADD) { - rc = memblock_add(*base, *lmb_size); - rc = (rc < 0) ? -EINVAL : 0; - } else if (action == PSERIES_DRCONF_MEM_REMOVE) { - rc = pseries_remove_memblock(*base, *lmb_size); - } else { - rc = -EINVAL; + /* The first int of the property is the number of lmb's described + * by the property. This is followed by an array of of_drconf_cell + * entries. Get the niumber of entries and skip to the array of + * of_drconf_cell's. + */ + entries = *p++; + old_drmem = (struct of_drconf_cell *)p; + + p = (u32 *)pr->prop->value; + p++; + new_drmem = (struct of_drconf_cell *)p; + + for (i = 0; i < entries; i++) { + if ((old_drmem[i].flags & DRCONF_MEM_ASSIGNED) && + (!(new_drmem[i].flags & DRCONF_MEM_ASSIGNED))) { + rc = pseries_remove_memblock(old_drmem[i].base_addr, + memblock_size); + break; + } else if ((!(old_drmem[i].flags & DRCONF_MEM_ASSIGNED)) && + (new_drmem[i].flags & DRCONF_MEM_ASSIGNED)) { + rc = memblock_add(old_drmem[i].base_addr, + memblock_size); + rc = (rc < 0) ? -EINVAL : 0; + break; + } } - of_node_put(np); return rc; } static int pseries_memory_notifier(struct notifier_block *nb, - unsigned long action, void *node) + unsigned long action, void *node) { - int err = NOTIFY_OK; + struct of_prop_reconfig *pr; + int err = 0; switch (action) { - case PSERIES_RECONFIG_ADD: - if (pseries_add_memory(node)) - err = NOTIFY_BAD; - break; - case PSERIES_RECONFIG_REMOVE: - if (pseries_remove_memory(node)) - err = NOTIFY_BAD; + case OF_RECONFIG_ATTACH_NODE: + err = pseries_add_mem_node(node); break; - case PSERIES_DRCONF_MEM_ADD: - case PSERIES_DRCONF_MEM_REMOVE: - if (pseries_drconf_memory(node, action)) - err = NOTIFY_BAD; + case OF_RECONFIG_DETACH_NODE: + err = pseries_remove_mem_node(node); break; - default: - err = NOTIFY_DONE; + case OF_RECONFIG_UPDATE_PROPERTY: + pr = (struct of_prop_reconfig *)node; + if (!strcmp(pr->prop->name, "ibm,dynamic-memory")) + err = pseries_update_drconf_memory(pr); break; } - return err; + return notifier_from_errno(err); } static struct notifier_block pseries_mem_nb = { @@ -187,7 +257,11 @@ static struct notifier_block pseries_mem_nb = { static int __init pseries_memory_hotplug_init(void) { if (firmware_has_feature(FW_FEATURE_LPAR)) - pSeries_reconfig_notifier_register(&pseries_mem_nb); + of_reconfig_notifier_register(&pseries_mem_nb); + +#ifdef CONFIG_MEMORY_HOTREMOVE + ppc_md.remove_memory = pseries_remove_memory; +#endif return 0; } |
