Loading arch/powerpc/include/asm/rtas.h +26 −0 Original line number Diff line number Diff line Loading @@ -274,6 +274,7 @@ inline uint32_t rtas_ext_event_company_id(struct rtas_ext_event_log_v6 *ext_log) #define PSERIES_ELOG_SECT_ID_MANUFACT_INFO (('M' << 8) | 'I') #define PSERIES_ELOG_SECT_ID_CALL_HOME (('C' << 8) | 'H') #define PSERIES_ELOG_SECT_ID_USER_DEF (('U' << 8) | 'D') #define PSERIES_ELOG_SECT_ID_HOTPLUG (('H' << 8) | 'P') /* Vendor specific Platform Event Log Format, Version 6, section header */ struct pseries_errorlog { Loading @@ -297,6 +298,31 @@ inline uint16_t pseries_errorlog_length(struct pseries_errorlog *sect) return be16_to_cpu(sect->length); } /* RTAS pseries hotplug errorlog section */ struct pseries_hp_errorlog { u8 resource; u8 action; u8 id_type; u8 reserved; union { __be32 drc_index; __be32 drc_count; char drc_name[1]; } _drc_u; }; #define PSERIES_HP_ELOG_RESOURCE_CPU 1 #define PSERIES_HP_ELOG_RESOURCE_MEM 2 #define PSERIES_HP_ELOG_RESOURCE_SLOT 3 #define PSERIES_HP_ELOG_RESOURCE_PHB 4 #define PSERIES_HP_ELOG_ACTION_ADD 1 #define PSERIES_HP_ELOG_ACTION_REMOVE 2 #define PSERIES_HP_ELOG_ID_DRC_NAME 1 #define PSERIES_HP_ELOG_ID_DRC_INDEX 2 #define PSERIES_HP_ELOG_ID_DRC_COUNT 3 struct pseries_errorlog *get_pseries_errorlog(struct rtas_error_log *log, uint16_t section_id); Loading arch/powerpc/platforms/pseries/dlpar.c +116 −2 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ * 2 as published by the Free Software Foundation. */ #define pr_fmt(fmt) "dlpar: " fmt #include <linux/kernel.h> #include <linux/notifier.h> #include <linux/spinlock.h> Loading Loading @@ -535,13 +537,125 @@ static ssize_t dlpar_cpu_release(const char *buf, size_t count) return count; } #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ static int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog) { int rc; /* pseries error logs are in BE format, convert to cpu type */ switch (hp_elog->id_type) { case PSERIES_HP_ELOG_ID_DRC_COUNT: hp_elog->_drc_u.drc_count = be32_to_cpu(hp_elog->_drc_u.drc_count); break; case PSERIES_HP_ELOG_ID_DRC_INDEX: hp_elog->_drc_u.drc_index = be32_to_cpu(hp_elog->_drc_u.drc_index); } switch (hp_elog->resource) { case PSERIES_HP_ELOG_RESOURCE_MEM: rc = dlpar_memory(hp_elog); break; default: pr_warn_ratelimited("Invalid resource (%d) specified\n", hp_elog->resource); rc = -EINVAL; } return rc; } static ssize_t dlpar_store(struct class *class, struct class_attribute *attr, const char *buf, size_t count) { struct pseries_hp_errorlog *hp_elog; const char *arg; int rc; hp_elog = kzalloc(sizeof(*hp_elog), GFP_KERNEL); if (!hp_elog) { rc = -ENOMEM; goto dlpar_store_out; } /* Parse out the request from the user, this will be in the form * <resource> <action> <id_type> <id> */ arg = buf; if (!strncmp(arg, "memory", 6)) { hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_MEM; arg += strlen("memory "); } else { pr_err("Invalid resource specified: \"%s\"\n", buf); rc = -EINVAL; goto dlpar_store_out; } if (!strncmp(arg, "add", 3)) { hp_elog->action = PSERIES_HP_ELOG_ACTION_ADD; arg += strlen("add "); } else if (!strncmp(arg, "remove", 6)) { hp_elog->action = PSERIES_HP_ELOG_ACTION_REMOVE; arg += strlen("remove "); } else { pr_err("Invalid action specified: \"%s\"\n", buf); rc = -EINVAL; goto dlpar_store_out; } if (!strncmp(arg, "index", 5)) { u32 index; hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_INDEX; arg += strlen("index "); if (kstrtou32(arg, 0, &index)) { rc = -EINVAL; pr_err("Invalid drc_index specified: \"%s\"\n", buf); goto dlpar_store_out; } hp_elog->_drc_u.drc_index = cpu_to_be32(index); } else if (!strncmp(arg, "count", 5)) { u32 count; hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_COUNT; arg += strlen("count "); if (kstrtou32(arg, 0, &count)) { rc = -EINVAL; pr_err("Invalid count specified: \"%s\"\n", buf); goto dlpar_store_out; } hp_elog->_drc_u.drc_count = cpu_to_be32(count); } else { pr_err("Invalid id_type specified: \"%s\"\n", buf); rc = -EINVAL; goto dlpar_store_out; } rc = handle_dlpar_errorlog(hp_elog); dlpar_store_out: kfree(hp_elog); return rc ? rc : count; } static CLASS_ATTR(dlpar, S_IWUSR, NULL, dlpar_store); static int __init pseries_dlpar_init(void) { int rc; #ifdef CONFIG_ARCH_CPU_PROBE_RELEASE ppc_md.cpu_probe = dlpar_cpu_probe; ppc_md.cpu_release = dlpar_cpu_release; #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ return 0; rc = sysfs_create_file(kernel_kobj, &class_attr_dlpar.attr); return rc; } machine_device_initcall(pseries, pseries_dlpar_init); #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ arch/powerpc/platforms/pseries/hotplug-memory.c +473 −0 Original line number Diff line number Diff line Loading @@ -9,11 +9,14 @@ * 2 of the License, or (at your option) any later version. */ #define pr_fmt(fmt) "pseries-hotplug-mem: " fmt #include <linux/of.h> #include <linux/of_address.h> #include <linux/memblock.h> #include <linux/memory.h> #include <linux/memory_hotplug.h> #include <linux/slab.h> #include <asm/firmware.h> #include <asm/machdep.h> Loading @@ -21,6 +24,8 @@ #include <asm/sparsemem.h> #include "pseries.h" static bool rtas_hp_event; unsigned long pseries_memory_block_size(void) { struct device_node *np; Loading Loading @@ -64,6 +69,67 @@ unsigned long pseries_memory_block_size(void) return memblock_size; } static void dlpar_free_drconf_property(struct property *prop) { kfree(prop->name); kfree(prop->value); kfree(prop); } static struct property *dlpar_clone_drconf_property(struct device_node *dn) { struct property *prop, *new_prop; struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int i; prop = of_find_property(dn, "ibm,dynamic-memory", NULL); if (!prop) return NULL; new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); if (!new_prop) return NULL; new_prop->name = kstrdup(prop->name, GFP_KERNEL); new_prop->value = kmalloc(prop->length, GFP_KERNEL); if (!new_prop->name || !new_prop->value) { dlpar_free_drconf_property(new_prop); return NULL; } memcpy(new_prop->value, prop->value, prop->length); new_prop->length = prop->length; /* Convert the property to cpu endian-ness */ p = new_prop->value; *p = be32_to_cpu(*p); num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; for (i = 0; i < num_lmbs; i++) { lmbs[i].base_addr = be64_to_cpu(lmbs[i].base_addr); lmbs[i].drc_index = be32_to_cpu(lmbs[i].drc_index); lmbs[i].flags = be32_to_cpu(lmbs[i].flags); } return new_prop; } static struct memory_block *lmb_to_memblock(struct of_drconf_cell *lmb) { unsigned long section_nr; struct mem_section *mem_sect; struct memory_block *mem_block; section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr)); mem_sect = __nr_to_section(section_nr); mem_block = find_memory_block(mem_sect); return mem_block; } #ifdef CONFIG_MEMORY_HOTREMOVE static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) { Loading Loading @@ -122,6 +188,173 @@ static int pseries_remove_mem_node(struct device_node *np) pseries_remove_memblock(base, lmb_size); return 0; } static bool lmb_is_removable(struct of_drconf_cell *lmb) { int i, scns_per_block; int rc = 1; unsigned long pfn, block_sz; u64 phys_addr; if (!(lmb->flags & DRCONF_MEM_ASSIGNED)) return false; block_sz = memory_block_size_bytes(); scns_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE; phys_addr = lmb->base_addr; for (i = 0; i < scns_per_block; i++) { pfn = PFN_DOWN(phys_addr); if (!pfn_present(pfn)) continue; rc &= is_mem_section_removable(pfn, PAGES_PER_SECTION); phys_addr += MIN_MEMORY_BLOCK_SIZE; } return rc ? true : false; } static int dlpar_add_lmb(struct of_drconf_cell *); static int dlpar_remove_lmb(struct of_drconf_cell *lmb) { struct memory_block *mem_block; unsigned long block_sz; int nid, rc; if (!lmb_is_removable(lmb)) return -EINVAL; mem_block = lmb_to_memblock(lmb); if (!mem_block) return -EINVAL; rc = device_offline(&mem_block->dev); put_device(&mem_block->dev); if (rc) return rc; block_sz = pseries_memory_block_size(); nid = memory_add_physaddr_to_nid(lmb->base_addr); remove_memory(nid, lmb->base_addr, block_sz); /* Update memory regions for memory remove */ memblock_remove(lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); lmb->flags &= ~DRCONF_MEM_ASSIGNED; return 0; } static int dlpar_memory_remove_by_count(u32 lmbs_to_remove, struct property *prop) { struct of_drconf_cell *lmbs; int lmbs_removed = 0; int lmbs_available = 0; u32 num_lmbs, *p; int i, rc; pr_info("Attempting to hot-remove %d LMB(s)\n", lmbs_to_remove); if (lmbs_to_remove == 0) return -EINVAL; p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; /* Validate that there are enough LMBs to satisfy the request */ for (i = 0; i < num_lmbs; i++) { if (lmbs[i].flags & DRCONF_MEM_ASSIGNED) lmbs_available++; } if (lmbs_available < lmbs_to_remove) return -EINVAL; for (i = 0; i < num_lmbs && lmbs_removed < lmbs_to_remove; i++) { rc = dlpar_remove_lmb(&lmbs[i]); if (rc) continue; lmbs_removed++; /* Mark this lmb so we can add it later if all of the * requested LMBs cannot be removed. */ lmbs[i].reserved = 1; } if (lmbs_removed != lmbs_to_remove) { pr_err("Memory hot-remove failed, adding LMB's back\n"); for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; rc = dlpar_add_lmb(&lmbs[i]); if (rc) pr_err("Failed to add LMB back, drc index %x\n", lmbs[i].drc_index); lmbs[i].reserved = 0; } rc = -EINVAL; } else { for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; pr_info("Memory at %llx was hot-removed\n", lmbs[i].base_addr); lmbs[i].reserved = 0; } rc = 0; } return rc; } static int dlpar_memory_remove_by_index(u32 drc_index, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int lmb_found; int i, rc; pr_info("Attempting to hot-remove LMB, drc index %x\n", drc_index); p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; lmb_found = 0; for (i = 0; i < num_lmbs; i++) { if (lmbs[i].drc_index == drc_index) { lmb_found = 1; rc = dlpar_remove_lmb(&lmbs[i]); break; } } if (!lmb_found) rc = -EINVAL; if (rc) pr_info("Failed to hot-remove memory at %llx\n", lmbs[i].base_addr); else pr_info("Memory at %llx was hot-removed\n", lmbs[i].base_addr); return rc; } #else static inline int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) Loading @@ -132,8 +365,245 @@ static inline int pseries_remove_mem_node(struct device_node *np) { return 0; } static inline int dlpar_memory_remove(struct pseries_hp_errorlog *hp_elog) { return -EOPNOTSUPP; } #endif /* CONFIG_MEMORY_HOTREMOVE */ static int dlpar_add_lmb(struct of_drconf_cell *lmb) { struct memory_block *mem_block; unsigned long block_sz; int nid, rc; if (lmb->flags & DRCONF_MEM_ASSIGNED) return -EINVAL; block_sz = memory_block_size_bytes(); rc = dlpar_acquire_drc(lmb->drc_index); if (rc) return rc; /* Find the node id for this address */ nid = memory_add_physaddr_to_nid(lmb->base_addr); /* Add the memory */ rc = add_memory(nid, lmb->base_addr, block_sz); if (rc) { dlpar_release_drc(lmb->drc_index); return rc; } /* Register this block of memory */ rc = memblock_add(lmb->base_addr, block_sz); if (rc) { remove_memory(nid, lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); return rc; } mem_block = lmb_to_memblock(lmb); if (!mem_block) { remove_memory(nid, lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); return -EINVAL; } rc = device_online(&mem_block->dev); put_device(&mem_block->dev); if (rc) { remove_memory(nid, lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); return rc; } lmb->flags |= DRCONF_MEM_ASSIGNED; return 0; } static int dlpar_memory_add_by_count(u32 lmbs_to_add, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int lmbs_available = 0; int lmbs_added = 0; int i, rc; pr_info("Attempting to hot-add %d LMB(s)\n", lmbs_to_add); if (lmbs_to_add == 0) return -EINVAL; p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; /* Validate that there are enough LMBs to satisfy the request */ for (i = 0; i < num_lmbs; i++) { if (!(lmbs[i].flags & DRCONF_MEM_ASSIGNED)) lmbs_available++; } if (lmbs_available < lmbs_to_add) return -EINVAL; for (i = 0; i < num_lmbs && lmbs_to_add != lmbs_added; i++) { rc = dlpar_add_lmb(&lmbs[i]); if (rc) continue; lmbs_added++; /* Mark this lmb so we can remove it later if all of the * requested LMBs cannot be added. */ lmbs[i].reserved = 1; } if (lmbs_added != lmbs_to_add) { pr_err("Memory hot-add failed, removing any added LMBs\n"); for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; rc = dlpar_remove_lmb(&lmbs[i]); if (rc) pr_err("Failed to remove LMB, drc index %x\n", be32_to_cpu(lmbs[i].drc_index)); } rc = -EINVAL; } else { for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; pr_info("Memory at %llx (drc index %x) was hot-added\n", lmbs[i].base_addr, lmbs[i].drc_index); lmbs[i].reserved = 0; } } return rc; } static int dlpar_memory_add_by_index(u32 drc_index, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int i, lmb_found; int rc; pr_info("Attempting to hot-add LMB, drc index %x\n", drc_index); p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; lmb_found = 0; for (i = 0; i < num_lmbs; i++) { if (lmbs[i].drc_index == drc_index) { lmb_found = 1; rc = dlpar_add_lmb(&lmbs[i]); break; } } if (!lmb_found) rc = -EINVAL; if (rc) pr_info("Failed to hot-add memory, drc index %x\n", drc_index); else pr_info("Memory at %llx (drc index %x) was hot-added\n", lmbs[i].base_addr, drc_index); return rc; } static void dlpar_update_drconf_property(struct device_node *dn, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int i; /* Convert the property back to BE */ p = prop->value; num_lmbs = *p; *p = cpu_to_be32(*p); p++; lmbs = (struct of_drconf_cell *)p; for (i = 0; i < num_lmbs; i++) { lmbs[i].base_addr = cpu_to_be64(lmbs[i].base_addr); lmbs[i].drc_index = cpu_to_be32(lmbs[i].drc_index); lmbs[i].flags = cpu_to_be32(lmbs[i].flags); } rtas_hp_event = true; of_update_property(dn, prop); rtas_hp_event = false; } int dlpar_memory(struct pseries_hp_errorlog *hp_elog) { struct device_node *dn; struct property *prop; u32 count, drc_index; int rc; count = hp_elog->_drc_u.drc_count; drc_index = hp_elog->_drc_u.drc_index; lock_device_hotplug(); dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory"); if (!dn) return -EINVAL; prop = dlpar_clone_drconf_property(dn); if (!prop) { of_node_put(dn); return -EINVAL; } switch (hp_elog->action) { case PSERIES_HP_ELOG_ACTION_ADD: if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) rc = dlpar_memory_add_by_count(count, prop); else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) rc = dlpar_memory_add_by_index(drc_index, prop); else rc = -EINVAL; break; case PSERIES_HP_ELOG_ACTION_REMOVE: if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) rc = dlpar_memory_remove_by_count(count, prop); else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) rc = dlpar_memory_remove_by_index(drc_index, prop); else rc = -EINVAL; break; default: pr_err("Invalid action (%d) specified\n", hp_elog->action); rc = -EINVAL; break; } if (rc) dlpar_free_drconf_property(prop); else dlpar_update_drconf_property(dn, prop); of_node_put(dn); unlock_device_hotplug(); return rc; } static int pseries_add_mem_node(struct device_node *np) { const char *type; Loading Loading @@ -174,6 +644,9 @@ static int pseries_update_drconf_memory(struct of_reconfig_data *pr) __be32 *p; int i, rc = -EINVAL; if (rtas_hp_event) return 0; memblock_size = pseries_memory_block_size(); if (!memblock_size) return -EINVAL; Loading arch/powerpc/platforms/pseries/pseries.h +12 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ #define _PSERIES_PSERIES_H #include <linux/interrupt.h> #include <asm/rtas.h> struct device_node; Loading Loading @@ -60,6 +61,17 @@ extern struct device_node *dlpar_configure_connector(__be32, struct device_node *); extern int dlpar_attach_node(struct device_node *); extern int dlpar_detach_node(struct device_node *); extern int dlpar_acquire_drc(u32 drc_index); extern int dlpar_release_drc(u32 drc_index); #ifdef CONFIG_MEMORY_HOTPLUG int dlpar_memory(struct pseries_hp_errorlog *hp_elog); #else static inline int dlpar_memory(struct pseries_hp_errorlog *hp_elog) { return -EOPNOTSUPP; } #endif /* PCI root bridge prepare function override for pseries */ struct pci_host_bridge; Loading Loading
arch/powerpc/include/asm/rtas.h +26 −0 Original line number Diff line number Diff line Loading @@ -274,6 +274,7 @@ inline uint32_t rtas_ext_event_company_id(struct rtas_ext_event_log_v6 *ext_log) #define PSERIES_ELOG_SECT_ID_MANUFACT_INFO (('M' << 8) | 'I') #define PSERIES_ELOG_SECT_ID_CALL_HOME (('C' << 8) | 'H') #define PSERIES_ELOG_SECT_ID_USER_DEF (('U' << 8) | 'D') #define PSERIES_ELOG_SECT_ID_HOTPLUG (('H' << 8) | 'P') /* Vendor specific Platform Event Log Format, Version 6, section header */ struct pseries_errorlog { Loading @@ -297,6 +298,31 @@ inline uint16_t pseries_errorlog_length(struct pseries_errorlog *sect) return be16_to_cpu(sect->length); } /* RTAS pseries hotplug errorlog section */ struct pseries_hp_errorlog { u8 resource; u8 action; u8 id_type; u8 reserved; union { __be32 drc_index; __be32 drc_count; char drc_name[1]; } _drc_u; }; #define PSERIES_HP_ELOG_RESOURCE_CPU 1 #define PSERIES_HP_ELOG_RESOURCE_MEM 2 #define PSERIES_HP_ELOG_RESOURCE_SLOT 3 #define PSERIES_HP_ELOG_RESOURCE_PHB 4 #define PSERIES_HP_ELOG_ACTION_ADD 1 #define PSERIES_HP_ELOG_ACTION_REMOVE 2 #define PSERIES_HP_ELOG_ID_DRC_NAME 1 #define PSERIES_HP_ELOG_ID_DRC_INDEX 2 #define PSERIES_HP_ELOG_ID_DRC_COUNT 3 struct pseries_errorlog *get_pseries_errorlog(struct rtas_error_log *log, uint16_t section_id); Loading
arch/powerpc/platforms/pseries/dlpar.c +116 −2 Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ * 2 as published by the Free Software Foundation. */ #define pr_fmt(fmt) "dlpar: " fmt #include <linux/kernel.h> #include <linux/notifier.h> #include <linux/spinlock.h> Loading Loading @@ -535,13 +537,125 @@ static ssize_t dlpar_cpu_release(const char *buf, size_t count) return count; } #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ static int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog) { int rc; /* pseries error logs are in BE format, convert to cpu type */ switch (hp_elog->id_type) { case PSERIES_HP_ELOG_ID_DRC_COUNT: hp_elog->_drc_u.drc_count = be32_to_cpu(hp_elog->_drc_u.drc_count); break; case PSERIES_HP_ELOG_ID_DRC_INDEX: hp_elog->_drc_u.drc_index = be32_to_cpu(hp_elog->_drc_u.drc_index); } switch (hp_elog->resource) { case PSERIES_HP_ELOG_RESOURCE_MEM: rc = dlpar_memory(hp_elog); break; default: pr_warn_ratelimited("Invalid resource (%d) specified\n", hp_elog->resource); rc = -EINVAL; } return rc; } static ssize_t dlpar_store(struct class *class, struct class_attribute *attr, const char *buf, size_t count) { struct pseries_hp_errorlog *hp_elog; const char *arg; int rc; hp_elog = kzalloc(sizeof(*hp_elog), GFP_KERNEL); if (!hp_elog) { rc = -ENOMEM; goto dlpar_store_out; } /* Parse out the request from the user, this will be in the form * <resource> <action> <id_type> <id> */ arg = buf; if (!strncmp(arg, "memory", 6)) { hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_MEM; arg += strlen("memory "); } else { pr_err("Invalid resource specified: \"%s\"\n", buf); rc = -EINVAL; goto dlpar_store_out; } if (!strncmp(arg, "add", 3)) { hp_elog->action = PSERIES_HP_ELOG_ACTION_ADD; arg += strlen("add "); } else if (!strncmp(arg, "remove", 6)) { hp_elog->action = PSERIES_HP_ELOG_ACTION_REMOVE; arg += strlen("remove "); } else { pr_err("Invalid action specified: \"%s\"\n", buf); rc = -EINVAL; goto dlpar_store_out; } if (!strncmp(arg, "index", 5)) { u32 index; hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_INDEX; arg += strlen("index "); if (kstrtou32(arg, 0, &index)) { rc = -EINVAL; pr_err("Invalid drc_index specified: \"%s\"\n", buf); goto dlpar_store_out; } hp_elog->_drc_u.drc_index = cpu_to_be32(index); } else if (!strncmp(arg, "count", 5)) { u32 count; hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_COUNT; arg += strlen("count "); if (kstrtou32(arg, 0, &count)) { rc = -EINVAL; pr_err("Invalid count specified: \"%s\"\n", buf); goto dlpar_store_out; } hp_elog->_drc_u.drc_count = cpu_to_be32(count); } else { pr_err("Invalid id_type specified: \"%s\"\n", buf); rc = -EINVAL; goto dlpar_store_out; } rc = handle_dlpar_errorlog(hp_elog); dlpar_store_out: kfree(hp_elog); return rc ? rc : count; } static CLASS_ATTR(dlpar, S_IWUSR, NULL, dlpar_store); static int __init pseries_dlpar_init(void) { int rc; #ifdef CONFIG_ARCH_CPU_PROBE_RELEASE ppc_md.cpu_probe = dlpar_cpu_probe; ppc_md.cpu_release = dlpar_cpu_release; #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ return 0; rc = sysfs_create_file(kernel_kobj, &class_attr_dlpar.attr); return rc; } machine_device_initcall(pseries, pseries_dlpar_init); #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */
arch/powerpc/platforms/pseries/hotplug-memory.c +473 −0 Original line number Diff line number Diff line Loading @@ -9,11 +9,14 @@ * 2 of the License, or (at your option) any later version. */ #define pr_fmt(fmt) "pseries-hotplug-mem: " fmt #include <linux/of.h> #include <linux/of_address.h> #include <linux/memblock.h> #include <linux/memory.h> #include <linux/memory_hotplug.h> #include <linux/slab.h> #include <asm/firmware.h> #include <asm/machdep.h> Loading @@ -21,6 +24,8 @@ #include <asm/sparsemem.h> #include "pseries.h" static bool rtas_hp_event; unsigned long pseries_memory_block_size(void) { struct device_node *np; Loading Loading @@ -64,6 +69,67 @@ unsigned long pseries_memory_block_size(void) return memblock_size; } static void dlpar_free_drconf_property(struct property *prop) { kfree(prop->name); kfree(prop->value); kfree(prop); } static struct property *dlpar_clone_drconf_property(struct device_node *dn) { struct property *prop, *new_prop; struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int i; prop = of_find_property(dn, "ibm,dynamic-memory", NULL); if (!prop) return NULL; new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); if (!new_prop) return NULL; new_prop->name = kstrdup(prop->name, GFP_KERNEL); new_prop->value = kmalloc(prop->length, GFP_KERNEL); if (!new_prop->name || !new_prop->value) { dlpar_free_drconf_property(new_prop); return NULL; } memcpy(new_prop->value, prop->value, prop->length); new_prop->length = prop->length; /* Convert the property to cpu endian-ness */ p = new_prop->value; *p = be32_to_cpu(*p); num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; for (i = 0; i < num_lmbs; i++) { lmbs[i].base_addr = be64_to_cpu(lmbs[i].base_addr); lmbs[i].drc_index = be32_to_cpu(lmbs[i].drc_index); lmbs[i].flags = be32_to_cpu(lmbs[i].flags); } return new_prop; } static struct memory_block *lmb_to_memblock(struct of_drconf_cell *lmb) { unsigned long section_nr; struct mem_section *mem_sect; struct memory_block *mem_block; section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr)); mem_sect = __nr_to_section(section_nr); mem_block = find_memory_block(mem_sect); return mem_block; } #ifdef CONFIG_MEMORY_HOTREMOVE static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) { Loading Loading @@ -122,6 +188,173 @@ static int pseries_remove_mem_node(struct device_node *np) pseries_remove_memblock(base, lmb_size); return 0; } static bool lmb_is_removable(struct of_drconf_cell *lmb) { int i, scns_per_block; int rc = 1; unsigned long pfn, block_sz; u64 phys_addr; if (!(lmb->flags & DRCONF_MEM_ASSIGNED)) return false; block_sz = memory_block_size_bytes(); scns_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE; phys_addr = lmb->base_addr; for (i = 0; i < scns_per_block; i++) { pfn = PFN_DOWN(phys_addr); if (!pfn_present(pfn)) continue; rc &= is_mem_section_removable(pfn, PAGES_PER_SECTION); phys_addr += MIN_MEMORY_BLOCK_SIZE; } return rc ? true : false; } static int dlpar_add_lmb(struct of_drconf_cell *); static int dlpar_remove_lmb(struct of_drconf_cell *lmb) { struct memory_block *mem_block; unsigned long block_sz; int nid, rc; if (!lmb_is_removable(lmb)) return -EINVAL; mem_block = lmb_to_memblock(lmb); if (!mem_block) return -EINVAL; rc = device_offline(&mem_block->dev); put_device(&mem_block->dev); if (rc) return rc; block_sz = pseries_memory_block_size(); nid = memory_add_physaddr_to_nid(lmb->base_addr); remove_memory(nid, lmb->base_addr, block_sz); /* Update memory regions for memory remove */ memblock_remove(lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); lmb->flags &= ~DRCONF_MEM_ASSIGNED; return 0; } static int dlpar_memory_remove_by_count(u32 lmbs_to_remove, struct property *prop) { struct of_drconf_cell *lmbs; int lmbs_removed = 0; int lmbs_available = 0; u32 num_lmbs, *p; int i, rc; pr_info("Attempting to hot-remove %d LMB(s)\n", lmbs_to_remove); if (lmbs_to_remove == 0) return -EINVAL; p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; /* Validate that there are enough LMBs to satisfy the request */ for (i = 0; i < num_lmbs; i++) { if (lmbs[i].flags & DRCONF_MEM_ASSIGNED) lmbs_available++; } if (lmbs_available < lmbs_to_remove) return -EINVAL; for (i = 0; i < num_lmbs && lmbs_removed < lmbs_to_remove; i++) { rc = dlpar_remove_lmb(&lmbs[i]); if (rc) continue; lmbs_removed++; /* Mark this lmb so we can add it later if all of the * requested LMBs cannot be removed. */ lmbs[i].reserved = 1; } if (lmbs_removed != lmbs_to_remove) { pr_err("Memory hot-remove failed, adding LMB's back\n"); for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; rc = dlpar_add_lmb(&lmbs[i]); if (rc) pr_err("Failed to add LMB back, drc index %x\n", lmbs[i].drc_index); lmbs[i].reserved = 0; } rc = -EINVAL; } else { for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; pr_info("Memory at %llx was hot-removed\n", lmbs[i].base_addr); lmbs[i].reserved = 0; } rc = 0; } return rc; } static int dlpar_memory_remove_by_index(u32 drc_index, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int lmb_found; int i, rc; pr_info("Attempting to hot-remove LMB, drc index %x\n", drc_index); p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; lmb_found = 0; for (i = 0; i < num_lmbs; i++) { if (lmbs[i].drc_index == drc_index) { lmb_found = 1; rc = dlpar_remove_lmb(&lmbs[i]); break; } } if (!lmb_found) rc = -EINVAL; if (rc) pr_info("Failed to hot-remove memory at %llx\n", lmbs[i].base_addr); else pr_info("Memory at %llx was hot-removed\n", lmbs[i].base_addr); return rc; } #else static inline int pseries_remove_memblock(unsigned long base, unsigned int memblock_size) Loading @@ -132,8 +365,245 @@ static inline int pseries_remove_mem_node(struct device_node *np) { return 0; } static inline int dlpar_memory_remove(struct pseries_hp_errorlog *hp_elog) { return -EOPNOTSUPP; } #endif /* CONFIG_MEMORY_HOTREMOVE */ static int dlpar_add_lmb(struct of_drconf_cell *lmb) { struct memory_block *mem_block; unsigned long block_sz; int nid, rc; if (lmb->flags & DRCONF_MEM_ASSIGNED) return -EINVAL; block_sz = memory_block_size_bytes(); rc = dlpar_acquire_drc(lmb->drc_index); if (rc) return rc; /* Find the node id for this address */ nid = memory_add_physaddr_to_nid(lmb->base_addr); /* Add the memory */ rc = add_memory(nid, lmb->base_addr, block_sz); if (rc) { dlpar_release_drc(lmb->drc_index); return rc; } /* Register this block of memory */ rc = memblock_add(lmb->base_addr, block_sz); if (rc) { remove_memory(nid, lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); return rc; } mem_block = lmb_to_memblock(lmb); if (!mem_block) { remove_memory(nid, lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); return -EINVAL; } rc = device_online(&mem_block->dev); put_device(&mem_block->dev); if (rc) { remove_memory(nid, lmb->base_addr, block_sz); dlpar_release_drc(lmb->drc_index); return rc; } lmb->flags |= DRCONF_MEM_ASSIGNED; return 0; } static int dlpar_memory_add_by_count(u32 lmbs_to_add, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int lmbs_available = 0; int lmbs_added = 0; int i, rc; pr_info("Attempting to hot-add %d LMB(s)\n", lmbs_to_add); if (lmbs_to_add == 0) return -EINVAL; p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; /* Validate that there are enough LMBs to satisfy the request */ for (i = 0; i < num_lmbs; i++) { if (!(lmbs[i].flags & DRCONF_MEM_ASSIGNED)) lmbs_available++; } if (lmbs_available < lmbs_to_add) return -EINVAL; for (i = 0; i < num_lmbs && lmbs_to_add != lmbs_added; i++) { rc = dlpar_add_lmb(&lmbs[i]); if (rc) continue; lmbs_added++; /* Mark this lmb so we can remove it later if all of the * requested LMBs cannot be added. */ lmbs[i].reserved = 1; } if (lmbs_added != lmbs_to_add) { pr_err("Memory hot-add failed, removing any added LMBs\n"); for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; rc = dlpar_remove_lmb(&lmbs[i]); if (rc) pr_err("Failed to remove LMB, drc index %x\n", be32_to_cpu(lmbs[i].drc_index)); } rc = -EINVAL; } else { for (i = 0; i < num_lmbs; i++) { if (!lmbs[i].reserved) continue; pr_info("Memory at %llx (drc index %x) was hot-added\n", lmbs[i].base_addr, lmbs[i].drc_index); lmbs[i].reserved = 0; } } return rc; } static int dlpar_memory_add_by_index(u32 drc_index, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int i, lmb_found; int rc; pr_info("Attempting to hot-add LMB, drc index %x\n", drc_index); p = prop->value; num_lmbs = *p++; lmbs = (struct of_drconf_cell *)p; lmb_found = 0; for (i = 0; i < num_lmbs; i++) { if (lmbs[i].drc_index == drc_index) { lmb_found = 1; rc = dlpar_add_lmb(&lmbs[i]); break; } } if (!lmb_found) rc = -EINVAL; if (rc) pr_info("Failed to hot-add memory, drc index %x\n", drc_index); else pr_info("Memory at %llx (drc index %x) was hot-added\n", lmbs[i].base_addr, drc_index); return rc; } static void dlpar_update_drconf_property(struct device_node *dn, struct property *prop) { struct of_drconf_cell *lmbs; u32 num_lmbs, *p; int i; /* Convert the property back to BE */ p = prop->value; num_lmbs = *p; *p = cpu_to_be32(*p); p++; lmbs = (struct of_drconf_cell *)p; for (i = 0; i < num_lmbs; i++) { lmbs[i].base_addr = cpu_to_be64(lmbs[i].base_addr); lmbs[i].drc_index = cpu_to_be32(lmbs[i].drc_index); lmbs[i].flags = cpu_to_be32(lmbs[i].flags); } rtas_hp_event = true; of_update_property(dn, prop); rtas_hp_event = false; } int dlpar_memory(struct pseries_hp_errorlog *hp_elog) { struct device_node *dn; struct property *prop; u32 count, drc_index; int rc; count = hp_elog->_drc_u.drc_count; drc_index = hp_elog->_drc_u.drc_index; lock_device_hotplug(); dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory"); if (!dn) return -EINVAL; prop = dlpar_clone_drconf_property(dn); if (!prop) { of_node_put(dn); return -EINVAL; } switch (hp_elog->action) { case PSERIES_HP_ELOG_ACTION_ADD: if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) rc = dlpar_memory_add_by_count(count, prop); else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) rc = dlpar_memory_add_by_index(drc_index, prop); else rc = -EINVAL; break; case PSERIES_HP_ELOG_ACTION_REMOVE: if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) rc = dlpar_memory_remove_by_count(count, prop); else if (hp_elog->id_type == PSERIES_HP_ELOG_ID_DRC_INDEX) rc = dlpar_memory_remove_by_index(drc_index, prop); else rc = -EINVAL; break; default: pr_err("Invalid action (%d) specified\n", hp_elog->action); rc = -EINVAL; break; } if (rc) dlpar_free_drconf_property(prop); else dlpar_update_drconf_property(dn, prop); of_node_put(dn); unlock_device_hotplug(); return rc; } static int pseries_add_mem_node(struct device_node *np) { const char *type; Loading Loading @@ -174,6 +644,9 @@ static int pseries_update_drconf_memory(struct of_reconfig_data *pr) __be32 *p; int i, rc = -EINVAL; if (rtas_hp_event) return 0; memblock_size = pseries_memory_block_size(); if (!memblock_size) return -EINVAL; Loading
arch/powerpc/platforms/pseries/pseries.h +12 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ #define _PSERIES_PSERIES_H #include <linux/interrupt.h> #include <asm/rtas.h> struct device_node; Loading Loading @@ -60,6 +61,17 @@ extern struct device_node *dlpar_configure_connector(__be32, struct device_node *); extern int dlpar_attach_node(struct device_node *); extern int dlpar_detach_node(struct device_node *); extern int dlpar_acquire_drc(u32 drc_index); extern int dlpar_release_drc(u32 drc_index); #ifdef CONFIG_MEMORY_HOTPLUG int dlpar_memory(struct pseries_hp_errorlog *hp_elog); #else static inline int dlpar_memory(struct pseries_hp_errorlog *hp_elog) { return -EOPNOTSUPP; } #endif /* PCI root bridge prepare function override for pseries */ struct pci_host_bridge; Loading