[PATCH v3 5/5] pseries: Implement memory hotplug remove in the kernel

2015-02-10 Thread Nathan Fontenot
This patch adds the ability to do memory hotplug remove in the kernel.

Currently the operation to hotplug remove 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 remove faster, and provide a common
code path to do memory hotplug for both the PowerVM and PowerKVM environments.

Signed-off-by: Nathan Fontenot nf...@linux.vnet.ibm.com
---
Changes from previous cversion:
Changes from previous version:
- converted uint32_t to u32
- Updated the messaging to properly report the success/failure of memory adds

 arch/powerpc/platforms/pseries/hotplug-memory.c |  192 +++
 1 file changed, 191 insertions(+), 1 deletion(-)

diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c 
b/arch/powerpc/platforms/pseries/hotplug-memory.c
index f5eec0f..742ef88 100644
--- a/arch/powerpc/platforms/pseries/hotplug-memory.c
+++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
@@ -188,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;
+   }
+
+ 

Re: [5/5] pseries: Implement memory hotplug remove in the kernel

2014-09-17 Thread Michael Ellerman

On Mon, 2014-09-15 at 15:33 -0500, Nathan Fontenot wrote:
 This patch adds the ability to do memory hotplug remove in the kernel.
 
 Currently the hotplug add/remove of memory is handled by the drmgr
 command. The drmgr command performs the add/remove 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 and remove faster, and provide a common place to do memory hotplug
 for both the PowerVM and PowerKVM environments.
 
 Signed-off-by: Nathan Fontenot nf...@linux.vnet.ibm.com
 ---
  arch/powerpc/platforms/pseries/hotplug-memory.c |  140 
 +++
  1 file changed, 139 insertions(+), 1 deletion(-)
 
 diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c 
 b/arch/powerpc/platforms/pseries/hotplug-memory.c
 index b254773..160c424 100644
 --- a/arch/powerpc/platforms/pseries/hotplug-memory.c
 +++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
 @@ -193,7 +193,137 @@ static int pseries_remove_mem_node(struct device_node 
 *np)
   pseries_remove_memblock(base, lmb_size);
   return 0;
  }
 +
 +static int lmb_is_removable(struct of_drconf_cell *lmb)
 +{

Do we not already have something like this?

 + int i, scns_per_block;
 + int rc = 1;

I can see this makes the = work below.

But what if block_sz / MIN_MEMORY_BLOCK_SIZE = 0 ?

 + unsigned long pfn, block_sz;
 + u64 phys_addr;
 +
 + phys_addr = be64_to_cpu(lmb-base_addr);
 + block_sz = memory_block_size_bytes();
 + scns_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE;
 +
 + 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;
 +}

 +static int dlpar_memory_remove(struct pseries_hp_errorlog *hp_elog)
 +{

...

 +}

Most of the same comments as for add.

cheers



___
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev

Re: [5/5] pseries: Implement memory hotplug remove in the kernel

2014-09-17 Thread Nathan Fontenot
On 09/17/2014 02:07 AM, Michael Ellerman wrote:
 
 On Mon, 2014-09-15 at 15:33 -0500, Nathan Fontenot wrote:
 This patch adds the ability to do memory hotplug remove in the kernel.

 Currently the hotplug add/remove of memory is handled by the drmgr
 command. The drmgr command performs the add/remove 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 and remove faster, and provide a common place to do memory hotplug
 for both the PowerVM and PowerKVM environments.

 Signed-off-by: Nathan Fontenot nf...@linux.vnet.ibm.com
 ---
  arch/powerpc/platforms/pseries/hotplug-memory.c |  140 
 +++
  1 file changed, 139 insertions(+), 1 deletion(-)

 diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c 
 b/arch/powerpc/platforms/pseries/hotplug-memory.c
 index b254773..160c424 100644
 --- a/arch/powerpc/platforms/pseries/hotplug-memory.c
 +++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
 @@ -193,7 +193,137 @@ static int pseries_remove_mem_node(struct device_node 
 *np)
  pseries_remove_memblock(base, lmb_size);
  return 0;
  }
 +
 +static int lmb_is_removable(struct of_drconf_cell *lmb)
 +{
 
 Do we not already have something like this?

No. Perhaps your thinking of the code in drivers/base/memory.c that
handles the sysfs removable file. That code just calls the same
is_mem_section_removable() routine.

 
 +int i, scns_per_block;
 +int rc = 1;
 
 I can see this makes the = work below.
 
 But what if block_sz / MIN_MEMORY_BLOCK_SIZE = 0 ?

If that happens, something else is really wrong. Most
likely a malformed device tree.

For pseries MIN_MEMORY_BLOCK_SIZE is defined to be the smallest
LMB size we suppport, 16MB.

I can add a pr_warn() statement here and bail if that happens.

 
 +unsigned long pfn, block_sz;
 +u64 phys_addr;
 +
 +phys_addr = be64_to_cpu(lmb-base_addr);
 +block_sz = memory_block_size_bytes();
 +scns_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE;
 +
 +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;
 +}
 
 +static int dlpar_memory_remove(struct pseries_hp_errorlog *hp_elog)
 +{
 
 ...
 
 +}
 
 Most of the same comments as for add.
 

ok, I'll go through them and apply them to the remove code.

Thanks for the review.

-Nathan

___
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev

[PATCH 5/5] pseries: Implement memory hotplug remove in the kernel

2014-09-15 Thread Nathan Fontenot
This patch adds the ability to do memory hotplug remove in the kernel.

Currently the hotplug add/remove of memory is handled by the drmgr
command. The drmgr command performs the add/remove 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 and remove faster, and provide a common place to do memory hotplug
for both the PowerVM and PowerKVM environments.

Signed-off-by: Nathan Fontenot nf...@linux.vnet.ibm.com
---
 arch/powerpc/platforms/pseries/hotplug-memory.c |  140 +++
 1 file changed, 139 insertions(+), 1 deletion(-)

diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c 
b/arch/powerpc/platforms/pseries/hotplug-memory.c
index b254773..160c424 100644
--- a/arch/powerpc/platforms/pseries/hotplug-memory.c
+++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
@@ -193,7 +193,137 @@ static int pseries_remove_mem_node(struct device_node *np)
pseries_remove_memblock(base, lmb_size);
return 0;
 }
+
+static int lmb_is_removable(struct of_drconf_cell *lmb)
+{
+   int i, scns_per_block;
+   int rc = 1;
+   unsigned long pfn, block_sz;
+   u64 phys_addr;
+
+   phys_addr = be64_to_cpu(lmb-base_addr);
+   block_sz = memory_block_size_bytes();
+   scns_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE;
+
+   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;
+}
+
+static int dlpar_add_one_lmb(struct of_drconf_cell *);
+
+static int dlpar_remove_one_lmb(struct of_drconf_cell *lmb)
+{
+   struct memory_block *mem_block;
+   unsigned long block_sz;
+   u64 phys_addr;
+   int nid, rc;
+
+   block_sz = memory_block_size_bytes();
+   phys_addr = be64_to_cpu(lmb-base_addr);
+   nid = memory_add_physaddr_to_nid(phys_addr);
+
+   if (!pfn_valid(phys_addr  PAGE_SHIFT)) {
+   memblock_remove(phys_addr, block_sz);
+   return 0;
+   }
+
+   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;
+
+   remove_memory(nid, phys_addr, block_sz);
+   memblock_remove(phys_addr, block_sz);
+
+   return 0;
+}
+
+static int dlpar_memory_remove(struct pseries_hp_errorlog *hp_elog)
+{
+   struct of_drconf_cell *lmb;
+   struct device_node *dn;
+   struct property *prop;
+   int lmbs_to_remove, lmbs_removed = 0;
+   int i, entries;
+   int rc = -EINVAL;
+   uint32_t *p;
+
+   if (hp_elog-id_type == PSERIES_HP_ELOG_ID_DRC_COUNT) {
+   lmbs_to_remove = be32_to_cpu(hp_elog-_drc_u.drc_count);
+   pr_info(Attempting to hot-remove %d LMB(s)\n, lmbs_to_remove);
+   } else {
+   lmbs_to_remove = 1;
+   pr_info(Attempting to hot-remove LMB, drc index %x\n,
+   be32_to_cpu(hp_elog-_drc_u.drc_index));
+   }
+
+   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;
+   }
+
+   p = prop-value;
+   entries = be32_to_cpu(*p++);
+   lmb = (struct of_drconf_cell *)p;
+
+   for (i = 0; i  entries; i++, lmb++) {
+   u32 drc_index = be32_to_cpu(lmb-drc_index);
+
+   if (lmbs_to_remove == lmbs_removed)
+   break;
+
+   if (hp_elog-id_type == PSERIES_HP_ELOG_ID_DRC_INDEX
+lmb-drc_index != hp_elog-_drc_u.drc_index)
+   continue;
+
+   if (!(be32_to_cpu(lmb-flags)  DRCONF_MEM_ASSIGNED)
+   || !lmb_is_removable(lmb))
+   continue;
+
+   rc = dlpar_remove_one_lmb(lmb);
+   if (rc)
+   continue;
+
+   rc = dlpar_release_drc(drc_index);
+   if (rc) {
+   dlpar_add_one_lmb(lmb);
+   continue;
+   }
+
+   lmb-flags = cpu_to_be32(~DRCONF_MEM_ASSIGNED);
+   lmbs_removed++;
+   pr_info(Memory at %llx (drc index %x) has been hot-removed\n,
+   be64_to_cpu(lmb-base_addr), drc_index);
+   }
+
+   if (lmbs_removed)
+   rc = of_update_property(dn, prop);
+   else
+   dlpar_free_drconf_property(prop);
+
+   of_node_put(dn);
+   return rc ? rc : lmbs_removed;
+}
+
 #else
+
 static inline int pseries_remove_memblock(unsigned long base,