OSDN Git Service

powerpc/pseries: Implement memory hotplug remove in the kernel
[sagit-ice-cold/kernel_xiaomi_msm8998.git] / arch / powerpc / platforms / pseries / hotplug-memory.c
index fa41f0d..742ef88 100644 (file)
@@ -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>
@@ -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;
@@ -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)
 {
@@ -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)
@@ -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;
@@ -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;