The Dynamic Logical Partitioning capabilities of the powerpc pseries platform
allows for the addition and removal of resources (i.e. CPU's, memory, and PCI
devices) from a partition. The removal of a resource involves
removing the resource's node from the device tree and then returning the
resource to firmware via the rtas set-indicator call.  To add a resource, it
is first obtained from firmware via the rtas set-indicator call and then a
new device tree node is created using the ibm,configure-coinnector rtas call
and added to the device tree.
This patch provides the kernel DLPAR infrastructure in a new filed named
dlpar.c.  The functionality provided is for acquiring and releasing a resource
from firmware and the parsing of information returned from the
ibm,configure-connector rtas call.  Additionally this exports the pSeries
reconfiguration notifier chain so that it can be invoked when device tree updates are made.

Signed-off-by: Nathan Fontenot <nf...@austin.ibm.com> --- arch/powerpc/include/asm/pSeries_reconfig.h | 1 arch/powerpc/platforms/pseries/Makefile | 2 arch/powerpc/platforms/pseries/dlpar.c | 344 ++++++++++++++++++++++++++++ arch/powerpc/platforms/pseries/reconfig.c | 2 4 files changed, 347 insertions(+), 2 deletions(-)

Index: powerpc/arch/powerpc/platforms/pseries/dlpar.c
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ powerpc/arch/powerpc/platforms/pseries/dlpar.c      2009-11-25 
04:54:13.000000000 -0600
@@ -0,0 +1,344 @@
+/*
+ * Support for dynamic reconfiguration for PCI, Memory, and CPU
+ * Hotplug and Dynamic Logical Partitioning on RPA platforms.
+ *
+ * Copyright (C) 2009 Nathan Fontenot
+ * Copyright (C) 2009 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+#include <linux/cpu.h>
+
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/uaccess.h>
+#include <asm/rtas.h>
+#include <asm/pSeries_reconfig.h>
+
+struct cc_workarea {
+       u32     drc_index;
+       u32     zero;
+       u32     name_offset;
+       u32     prop_length;
+       u32     prop_offset;
+};
+
+static void dlpar_free_cc_property(struct property *prop)
+{
+       kfree(prop->name);
+       kfree(prop->value);
+       kfree(prop);
+}
+
+static struct property *dlpar_parse_cc_property(struct cc_workarea *ccwa)
+{
+       struct property *prop;
+       char *name;
+       char *value;
+
+       prop = kzalloc(sizeof(*prop), GFP_KERNEL);
+       if (!prop)
+               return NULL;
+
+       name = (char *)ccwa + ccwa->name_offset;
+       prop->name = kstrdup(name, GFP_KERNEL);
+
+       prop->length = ccwa->prop_length;
+       value = (char *)ccwa + ccwa->prop_offset;
+       prop->value = kzalloc(prop->length, GFP_KERNEL);
+       if (!prop->value) {
+               dlpar_free_cc_property(prop);
+               return NULL;
+       }
+
+       memcpy(prop->value, value, prop->length);
+       return prop;
+}
+
+static struct device_node *dlpar_parse_cc_node(struct cc_workarea *ccwa)
+{
+       struct device_node *dn;
+       char *name;
+
+       dn = kzalloc(sizeof(*dn), GFP_KERNEL);
+       if (!dn)
+               return NULL;
+
+       /* The configure connector reported name does not contain a
+        * preceeding '/', so we allocate a buffer large enough to
+        * prepend this to the full_name.
+        */
+       name = (char *)ccwa + ccwa->name_offset;
+       dn->full_name = kmalloc(strlen(name) + 2, GFP_KERNEL);
+       if (!dn->full_name) {
+               kfree(dn);
+               return NULL;
+       }
+
+       sprintf(dn->full_name, "/%s", name);
+       return dn;
+}
+
+static void dlpar_free_one_cc_node(struct device_node *dn)
+{
+       struct property *prop;
+
+       while (dn->properties) {
+               prop = dn->properties;
+               dn->properties = prop->next;
+               dlpar_free_cc_property(prop);
+       }
+
+       kfree(dn->full_name);
+       kfree(dn);
+}
+
+static void dlpar_free_cc_nodes(struct device_node *dn)
+{
+       if (dn->child)
+               dlpar_free_cc_nodes(dn->child);
+
+       if (dn->sibling)
+               dlpar_free_cc_nodes(dn->sibling);
+
+       dlpar_free_one_cc_node(dn);
+}
+
+#define NEXT_SIBLING    1
+#define NEXT_CHILD      2
+#define NEXT_PROPERTY   3
+#define PREV_PARENT     4
+#define MORE_MEMORY     5
+#define CALL_AGAIN     -2
+#define ERR_CFG_USE     -9003
+
+struct device_node *dlpar_configure_connector(u32 drc_index)
+{
+       struct device_node *dn;
+       struct device_node *first_dn = NULL;
+       struct device_node *last_dn = NULL;
+       struct property *property;
+       struct property *last_property = NULL;
+       struct cc_workarea *ccwa;
+       int cc_token;
+       int rc;
+
+       cc_token = rtas_token("ibm,configure-connector");
+       if (cc_token == RTAS_UNKNOWN_SERVICE)
+               return NULL;
+
+       spin_lock(&rtas_data_buf_lock);
+       ccwa = (struct cc_workarea *)&rtas_data_buf[0];
+       ccwa->drc_index = drc_index;
+       ccwa->zero = 0;
+
+       rc = rtas_call(cc_token, 2, 1, NULL, rtas_data_buf, NULL);
+       while (rc) {
+               switch (rc) {
+               case NEXT_SIBLING:
+                       dn = dlpar_parse_cc_node(ccwa);
+                       if (!dn)
+                               goto cc_error;
+
+                       dn->parent = last_dn->parent;
+                       last_dn->sibling = dn;
+                       last_dn = dn;
+                       break;
+
+               case NEXT_CHILD:
+                       dn = dlpar_parse_cc_node(ccwa);
+                       if (!dn)
+                               goto cc_error;
+
+                       if (!first_dn)
+                               first_dn = dn;
+                       else {
+                               dn->parent = last_dn;
+                               if (last_dn)
+                                       last_dn->child = dn;
+                       }
+
+                       last_dn = dn;
+                       break;
+
+               case NEXT_PROPERTY:
+                       property = dlpar_parse_cc_property(ccwa);
+                       if (!property)
+                               goto cc_error;
+
+                       if (!last_dn->properties)
+                               last_dn->properties = property;
+                       else
+                               last_property->next = property;
+
+                       last_property = property;
+                       break;
+
+               case PREV_PARENT:
+                       last_dn = last_dn->parent;
+                       break;
+
+               case CALL_AGAIN:
+                       break;
+
+               case MORE_MEMORY:
+               case ERR_CFG_USE:
+               default:
+                       printk(KERN_ERR "Unexpected Error (%d) "
+                              "returned from configure-connector\n", rc);
+                       goto cc_error;
+               }
+
+               rc = rtas_call(cc_token, 2, 1, NULL, rtas_data_buf, NULL);
+       }
+
+       spin_unlock(&rtas_data_buf_lock);
+       return first_dn;
+
+cc_error:
+       if (first_dn)
+               dlpar_free_cc_nodes(first_dn);
+       spin_unlock(&rtas_data_buf_lock);
+       return NULL;
+}
+
+static struct device_node *derive_parent(const char *path)
+{
+       struct device_node *parent;
+       char *last_slash;
+
+       last_slash = strrchr(path, '/');
+       if (last_slash == path) {
+               parent = of_find_node_by_path("/");
+       } else {
+               char *parent_path;
+               int parent_path_len = last_slash - path + 1;
+               parent_path = kmalloc(parent_path_len, GFP_KERNEL);
+               if (!parent_path)
+                       return NULL;
+
+               strlcpy(parent_path, path, parent_path_len);
+               parent = of_find_node_by_path(parent_path);
+               kfree(parent_path);
+       }
+
+       return parent;
+}
+
+int dlpar_attach_node(struct device_node *dn)
+{
+       struct proc_dir_entry *ent;
+       int rc;
+
+       of_node_set_flag(dn, OF_DYNAMIC);
+       kref_init(&dn->kref);
+       dn->parent = derive_parent(dn->full_name);
+       if (!dn->parent)
+               return -ENOMEM;
+
+       rc = blocking_notifier_call_chain(&pSeries_reconfig_chain,
+                                         PSERIES_RECONFIG_ADD, dn);
+       if (rc == NOTIFY_BAD) {
+               printk(KERN_ERR "Failed to add device node %s\n",
+                      dn->full_name);
+               return -ENOMEM; /* For now, safe to assume kmalloc failure */
+       }
+
+       of_attach_node(dn);
+
+#ifdef CONFIG_PROC_DEVICETREE
+       ent = proc_mkdir(strrchr(dn->full_name, '/') + 1, dn->parent->pde);
+       if (ent)
+               proc_device_tree_add_node(dn, ent);
+#endif
+
+       of_node_put(dn->parent);
+       return 0;
+}
+
+int dlpar_detach_node(struct device_node *dn)
+{
+       struct device_node *parent = dn->parent;
+       struct property *prop = dn->properties;
+
+#ifdef CONFIG_PROC_DEVICETREE
+       while (prop) {
+               remove_proc_entry(prop->name, dn->pde);
+               prop = prop->next;
+       }
+
+       if (dn->pde)
+               remove_proc_entry(dn->pde->name, parent->pde);
+#endif
+
+       blocking_notifier_call_chain(&pSeries_reconfig_chain,
+                           PSERIES_RECONFIG_REMOVE, dn);
+       of_detach_node(dn);
+       of_node_put(dn); /* Must decrement the refcount */
+
+       return 0;
+}
+
+#define DR_ENTITY_SENSE                9003
+#define DR_ENTITY_PRESENT      1
+#define DR_ENTITY_UNUSABLE     2
+#define ALLOCATION_STATE       9003
+#define ALLOC_UNUSABLE         0
+#define ALLOC_USABLE           1
+#define ISOLATION_STATE                9001
+#define ISOLATE                        0
+#define UNISOLATE              1
+
+int dlpar_acquire_drc(u32 drc_index)
+{
+       int dr_status, rc;
+
+       rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
+                      DR_ENTITY_SENSE, drc_index);
+       if (rc || dr_status != DR_ENTITY_UNUSABLE)
+               return -1;
+
+       rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_USABLE);
+       if (rc)
+               return rc;
+
+       rc = rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
+       if (rc) {
+               rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
+               return rc;
+       }
+
+       return 0;
+}
+
+int dlpar_release_drc(u32 drc_index)
+{
+       int dr_status, rc;
+
+       rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
+                      DR_ENTITY_SENSE, drc_index);
+       if (rc || dr_status != DR_ENTITY_PRESENT)
+               return -1;
+
+       rc = rtas_set_indicator(ISOLATION_STATE, drc_index, ISOLATE);
+       if (rc)
+               return rc;
+
+       rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
+       if (rc) {
+               rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
+               return rc;
+       }
+
+       return 0;
+}
+
+
Index: powerpc/arch/powerpc/platforms/pseries/Makefile
===================================================================
--- powerpc.orig/arch/powerpc/platforms/pseries/Makefile        2009-11-20 
17:53:54.000000000 -0600
+++ powerpc/arch/powerpc/platforms/pseries/Makefile     2009-11-20 
17:55:52.000000000 -0600
@@ -8,7 +8,7 @@

obj-y                   := lpar.o hvCall.o nvram.o reconfig.o \
                           setup.o iommu.o ras.o rtasd.o \
-                          firmware.o power.o
+                          firmware.o power.o dlpar.o
obj-$(CONFIG_SMP)       += smp.o
obj-$(CONFIG_XICS)      += xics.o
obj-$(CONFIG_SCANLOG)   += scanlog.o
Index: powerpc/arch/powerpc/include/asm/pSeries_reconfig.h
===================================================================
--- powerpc.orig/arch/powerpc/include/asm/pSeries_reconfig.h    2009-11-20 
17:53:54.000000000 -0600
+++ powerpc/arch/powerpc/include/asm/pSeries_reconfig.h 2009-11-20 
17:55:52.000000000 -0600
@@ -17,6 +17,7 @@
#ifdef CONFIG_PPC_PSERIES
extern int pSeries_reconfig_notifier_register(struct notifier_block *);
extern void pSeries_reconfig_notifier_unregister(struct notifier_block *);
+extern struct blocking_notifier_head pSeries_reconfig_chain;
#else /* !CONFIG_PPC_PSERIES */
static inline int pSeries_reconfig_notifier_register(struct notifier_block *nb)
{
Index: powerpc/arch/powerpc/platforms/pseries/reconfig.c
===================================================================
--- powerpc.orig/arch/powerpc/platforms/pseries/reconfig.c      2009-11-20 
17:53:54.000000000 -0600
+++ powerpc/arch/powerpc/platforms/pseries/reconfig.c   2009-11-20 
17:55:52.000000000 -0600
@@ -96,7 +96,7 @@
        return parent;
}

-static BLOCKING_NOTIFIER_HEAD(pSeries_reconfig_chain);
+BLOCKING_NOTIFIER_HEAD(pSeries_reconfig_chain);

int pSeries_reconfig_notifier_register(struct notifier_block *nb)
{
_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev

Reply via email to