diff options
Diffstat (limited to 'arch/powerpc/platforms/pseries/dlpar.c')
| -rw-r--r-- | arch/powerpc/platforms/pseries/dlpar.c | 455 |
1 files changed, 341 insertions, 114 deletions
diff --git a/arch/powerpc/platforms/pseries/dlpar.c b/arch/powerpc/platforms/pseries/dlpar.c index 39187696ee74..979487da6522 100644 --- a/arch/powerpc/platforms/pseries/dlpar.c +++ b/arch/powerpc/platforms/pseries/dlpar.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * 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. */ #define pr_fmt(fmt) "dlpar: " fmt @@ -22,18 +19,17 @@ #include "of_helpers.h" #include "pseries.h" -#include <asm/prom.h> #include <asm/machdep.h> #include <linux/uaccess.h> #include <asm/rtas.h> +#include <asm/rtas-work-area.h> +#include <asm/prom.h> static struct workqueue_struct *pseries_hp_wq; struct pseries_hp_work { struct work_struct work; struct pseries_hp_errorlog *errlog; - struct completion *hp_completion; - int *rc; }; struct cc_workarea { @@ -63,6 +59,10 @@ static struct property *dlpar_parse_cc_property(struct cc_workarea *ccwa) name = (char *)ccwa + be32_to_cpu(ccwa->name_offset); prop->name = kstrdup(name, GFP_KERNEL); + if (!prop->name) { + dlpar_free_cc_property(prop); + return NULL; + } prop->length = be32_to_cpu(ccwa->prop_length); value = (char *)ccwa + be32_to_cpu(ccwa->prop_offset); @@ -75,24 +75,17 @@ static struct property *dlpar_parse_cc_property(struct cc_workarea *ccwa) return prop; } -static struct device_node *dlpar_parse_cc_node(struct cc_workarea *ccwa, - const char *path) +static struct device_node *dlpar_parse_cc_node(struct cc_workarea *ccwa) { struct device_node *dn; - char *name; - - /* If parent node path is "/" advance path to NULL terminator to - * prevent double leading slashs in full_name. - */ - if (!path[1]) - path++; + const char *name; dn = kzalloc(sizeof(*dn), GFP_KERNEL); if (!dn) return NULL; - name = (char *)ccwa + be32_to_cpu(ccwa->name_offset); - dn->full_name = kasprintf(GFP_KERNEL, "%s/%s", path, name); + name = (const char *)ccwa + be32_to_cpu(ccwa->name_offset); + dn->full_name = kstrdup(name, GFP_KERNEL); if (!dn->full_name) { kfree(dn); return NULL; @@ -135,7 +128,6 @@ void dlpar_free_cc_nodes(struct device_node *dn) #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(__be32 drc_index, @@ -147,42 +139,34 @@ struct device_node *dlpar_configure_connector(__be32 drc_index, struct property *property; struct property *last_property = NULL; struct cc_workarea *ccwa; + struct rtas_work_area *work_area; char *data_buf; - const char *parent_path = parent->full_name; int cc_token; int rc = -1; - cc_token = rtas_token("ibm,configure-connector"); + cc_token = rtas_function_token(RTAS_FN_IBM_CONFIGURE_CONNECTOR); if (cc_token == RTAS_UNKNOWN_SERVICE) return NULL; - data_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); - if (!data_buf) - return NULL; + work_area = rtas_work_area_alloc(SZ_4K); + data_buf = rtas_work_area_raw_buf(work_area); ccwa = (struct cc_workarea *)&data_buf[0]; ccwa->drc_index = drc_index; ccwa->zero = 0; do { - /* Since we release the rtas_data_buf lock between configure - * connector calls we want to re-populate the rtas_data_buffer - * with the contents of the previous call. - */ - spin_lock(&rtas_data_buf_lock); - - memcpy(rtas_data_buf, data_buf, RTAS_DATA_BUF_SIZE); - rc = rtas_call(cc_token, 2, 1, NULL, rtas_data_buf, NULL); - memcpy(data_buf, rtas_data_buf, RTAS_DATA_BUF_SIZE); - - spin_unlock(&rtas_data_buf_lock); + do { + rc = rtas_call(cc_token, 2, 1, NULL, + rtas_work_area_phys(work_area), NULL); + } while (rtas_busy_delay(rc)); switch (rc) { case COMPLETE: break; case NEXT_SIBLING: - dn = dlpar_parse_cc_node(ccwa, parent_path); + dn = dlpar_parse_cc_node(ccwa); if (!dn) goto cc_error; @@ -192,10 +176,7 @@ struct device_node *dlpar_configure_connector(__be32 drc_index, break; case NEXT_CHILD: - if (first_dn) - parent_path = last_dn->full_name; - - dn = dlpar_parse_cc_node(ccwa, parent_path); + dn = dlpar_parse_cc_node(ccwa); if (!dn) goto cc_error; @@ -226,10 +207,6 @@ struct device_node *dlpar_configure_connector(__be32 drc_index, case PREV_PARENT: last_dn = last_dn->parent; - parent_path = last_dn->parent->full_name; - break; - - case CALL_AGAIN: break; case MORE_MEMORY: @@ -242,7 +219,7 @@ struct device_node *dlpar_configure_connector(__be32 drc_index, } while (rc); cc_error: - kfree(data_buf); + rtas_work_area_free(work_area); if (rc) { if (first_dn) @@ -254,22 +231,18 @@ cc_error: return first_dn; } -int dlpar_attach_node(struct device_node *dn) +int dlpar_attach_node(struct device_node *dn, struct device_node *parent) { int rc; - dn->parent = pseries_of_derive_parent(dn->full_name); - if (IS_ERR(dn->parent)) - return PTR_ERR(dn->parent); + dn->parent = parent; rc = of_attach_node(dn); if (rc) { - printk(KERN_ERR "Failed to add device node %s\n", - dn->full_name); + printk(KERN_ERR "Failed to add device node %pOF\n", dn); return rc; } - of_node_put(dn->parent); return 0; } @@ -278,18 +251,31 @@ int dlpar_detach_node(struct device_node *dn) struct device_node *child; int rc; - child = of_get_next_child(dn, NULL); - while (child) { + for_each_child_of_node(dn, child) dlpar_detach_node(child); - child = of_get_next_child(dn, child); - } rc = of_detach_node(dn); if (rc) return rc; + of_node_put(dn); + return 0; } +static int dlpar_changeset_attach_cc_nodes(struct of_changeset *ocs, + struct device_node *dn) +{ + int rc; + + rc = of_changeset_attach_node(ocs, dn); + + if (!rc && dn->child) + rc = dlpar_changeset_attach_cc_nodes(ocs, dn->child); + if (!rc && dn->sibling) + rc = dlpar_changeset_attach_cc_nodes(ocs, dn->sibling); + + return rc; +} #define DR_ENTITY_SENSE 9003 #define DR_ENTITY_PRESENT 1 @@ -305,8 +291,7 @@ 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); + rc = rtas_get_sensor(DR_ENTITY_SENSE, drc_index, &dr_status); if (rc || dr_status != DR_ENTITY_UNUSABLE) return -1; @@ -327,8 +312,7 @@ 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); + rc = rtas_get_sensor(DR_ENTITY_SENSE, drc_index, &dr_status); if (rc || dr_status != DR_ENTITY_PRESENT) return -1; @@ -345,27 +329,267 @@ int dlpar_release_drc(u32 drc_index) return 0; } -static int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog) +int dlpar_unisolate_drc(u32 drc_index) +{ + int dr_status, rc; + + rc = rtas_get_sensor(DR_ENTITY_SENSE, drc_index, &dr_status); + if (rc || dr_status != DR_ENTITY_PRESENT) + return -1; + + rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE); + + return 0; +} + +static struct device_node * +get_device_node_with_drc_index(u32 index) +{ + struct device_node *np = NULL; + u32 node_index; + int rc; + + for_each_node_with_property(np, "ibm,my-drc-index") { + rc = of_property_read_u32(np, "ibm,my-drc-index", + &node_index); + if (rc) { + pr_err("%s: %pOF: of_property_read_u32 %s: %d\n", + __func__, np, "ibm,my-drc-index", rc); + of_node_put(np); + return NULL; + } + + if (index == node_index) + break; + } + + return np; +} + +static struct device_node * +get_device_node_with_drc_info(u32 index) +{ + struct device_node *np = NULL; + struct of_drc_info drc; + struct property *info; + const __be32 *value; + u32 node_index; + int i, j, count; + + for_each_node_with_property(np, "ibm,drc-info") { + info = of_find_property(np, "ibm,drc-info", NULL); + if (info == NULL) { + /* XXX can this happen? */ + of_node_put(np); + return NULL; + } + value = of_prop_next_u32(info, NULL, &count); + if (value == NULL) + continue; + value++; + for (i = 0; i < count; i++) { + if (of_read_drc_info_cell(&info, &value, &drc)) + break; + if (index > drc.last_drc_index) + continue; + node_index = drc.drc_index_start; + for (j = 0; j < drc.num_sequential_elems; j++) { + if (index == node_index) + return np; + node_index += drc.sequential_inc; + } + } + } + + return NULL; +} + +static struct device_node * +get_device_node_with_drc_indexes(u32 drc_index) +{ + struct device_node *np = NULL; + u32 nr_indexes, index; + int i, rc; + + for_each_node_with_property(np, "ibm,drc-indexes") { + /* + * First element in the array is the total number of + * DRC indexes returned. + */ + rc = of_property_read_u32_index(np, "ibm,drc-indexes", + 0, &nr_indexes); + if (rc) + goto out_put_np; + + /* + * Retrieve DRC index from the list and return the + * device node if matched with the specified index. + */ + for (i = 0; i < nr_indexes; i++) { + rc = of_property_read_u32_index(np, "ibm,drc-indexes", + i+1, &index); + if (rc) + goto out_put_np; + + if (drc_index == index) + return np; + } + } + + return NULL; + +out_put_np: + of_node_put(np); + return NULL; +} + +static int dlpar_hp_dt_add(u32 index) +{ + struct device_node *np, *nodes; + struct of_changeset ocs; + int rc; + + /* + * Do not add device node(s) if already exists in the + * device tree. + */ + np = get_device_node_with_drc_index(index); + if (np) { + pr_err("%s: Adding device node for index (%d), but " + "already exists in the device tree\n", + __func__, index); + rc = -EINVAL; + goto out; + } + + /* + * Recent FW provides ibm,drc-info property. So search + * for the user specified DRC index from ibm,drc-info + * property. If this property is not available, search + * in the indexes array from ibm,drc-indexes property. + */ + np = get_device_node_with_drc_info(index); + + if (!np) { + np = get_device_node_with_drc_indexes(index); + if (!np) + return -EIO; + } + + /* Next, configure the connector. */ + nodes = dlpar_configure_connector(cpu_to_be32(index), np); + if (!nodes) { + rc = -EIO; + goto out; + } + + /* + * Add the new nodes from dlpar_configure_connector() onto + * the device-tree. + */ + of_changeset_init(&ocs); + rc = dlpar_changeset_attach_cc_nodes(&ocs, nodes); + + if (!rc) + rc = of_changeset_apply(&ocs); + else + dlpar_free_cc_nodes(nodes); + + of_changeset_destroy(&ocs); + +out: + of_node_put(np); + return rc; +} + +static int changeset_detach_node_recursive(struct of_changeset *ocs, + struct device_node *node) +{ + struct device_node *child; + int rc; + + for_each_child_of_node(node, child) { + rc = changeset_detach_node_recursive(ocs, child); + if (rc) { + of_node_put(child); + return rc; + } + } + + return of_changeset_detach_node(ocs, node); +} + +static int dlpar_hp_dt_remove(u32 drc_index) +{ + struct device_node *np; + struct of_changeset ocs; + u32 index; + int rc = 0; + + /* + * Prune all nodes with a matching index. + */ + of_changeset_init(&ocs); + + for_each_node_with_property(np, "ibm,my-drc-index") { + rc = of_property_read_u32(np, "ibm,my-drc-index", &index); + if (rc) { + pr_err("%s: %pOF: of_property_read_u32 %s: %d\n", + __func__, np, "ibm,my-drc-index", rc); + of_node_put(np); + goto out; + } + + if (index == drc_index) { + rc = changeset_detach_node_recursive(&ocs, np); + if (rc) { + of_node_put(np); + goto out; + } + } + } + + rc = of_changeset_apply(&ocs); + +out: + of_changeset_destroy(&ocs); + return rc; +} + +static int dlpar_hp_dt(struct pseries_hp_errorlog *phpe) { + u32 drc_index; 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); + if (phpe->id_type != PSERIES_HP_ELOG_ID_DRC_INDEX) + return -EINVAL; + + drc_index = be32_to_cpu(phpe->_drc_u.drc_index); + + lock_device_hotplug(); + + switch (phpe->action) { + case PSERIES_HP_ELOG_ACTION_ADD: + rc = dlpar_hp_dt_add(drc_index); break; - case PSERIES_HP_ELOG_ID_DRC_INDEX: - hp_elog->_drc_u.drc_index = - be32_to_cpu(hp_elog->_drc_u.drc_index); + case PSERIES_HP_ELOG_ACTION_REMOVE: + rc = dlpar_hp_dt_remove(drc_index); + break; + default: + pr_err("Invalid action (%d) specified\n", phpe->action); + rc = -EINVAL; break; - case PSERIES_HP_ELOG_ID_DRC_IC: - hp_elog->_drc_u.ic.count = - be32_to_cpu(hp_elog->_drc_u.ic.count); - hp_elog->_drc_u.ic.index = - be32_to_cpu(hp_elog->_drc_u.ic.index); } + unlock_device_hotplug(); + + return rc; +} + +int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog) +{ + int rc; + switch (hp_elog->resource) { case PSERIES_HP_ELOG_RESOURCE_MEM: rc = dlpar_memory(hp_elog); @@ -373,6 +597,13 @@ static int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog) case PSERIES_HP_ELOG_RESOURCE_CPU: rc = dlpar_cpu(hp_elog); break; + case PSERIES_HP_ELOG_RESOURCE_PMEM: + rc = dlpar_hp_pmem(hp_elog); + break; + case PSERIES_HP_ELOG_RESOURCE_DT: + rc = dlpar_hp_dt(hp_elog); + break; + default: pr_warn_ratelimited("Invalid resource (%d) specified\n", hp_elog->resource); @@ -387,39 +618,28 @@ static void pseries_hp_work_fn(struct work_struct *work) struct pseries_hp_work *hp_work = container_of(work, struct pseries_hp_work, work); - if (hp_work->rc) - *(hp_work->rc) = handle_dlpar_errorlog(hp_work->errlog); - else - handle_dlpar_errorlog(hp_work->errlog); - - if (hp_work->hp_completion) - complete(hp_work->hp_completion); + handle_dlpar_errorlog(hp_work->errlog); kfree(hp_work->errlog); - kfree((void *)work); + kfree(work); } -void queue_hotplug_event(struct pseries_hp_errorlog *hp_errlog, - struct completion *hotplug_done, int *rc) +void queue_hotplug_event(struct pseries_hp_errorlog *hp_errlog) { struct pseries_hp_work *work; struct pseries_hp_errorlog *hp_errlog_copy; - hp_errlog_copy = kmalloc(sizeof(struct pseries_hp_errorlog), - GFP_KERNEL); - memcpy(hp_errlog_copy, hp_errlog, sizeof(struct pseries_hp_errorlog)); + hp_errlog_copy = kmemdup(hp_errlog, sizeof(*hp_errlog), GFP_ATOMIC); + if (!hp_errlog_copy) + return; - work = kmalloc(sizeof(struct pseries_hp_work), GFP_KERNEL); + work = kmalloc(sizeof(struct pseries_hp_work), GFP_ATOMIC); if (work) { INIT_WORK((struct work_struct *)work, pseries_hp_work_fn); work->errlog = hp_errlog_copy; - work->hp_completion = hotplug_done; - work->rc = rc; queue_work(pseries_hp_wq, (struct work_struct *)work); } else { - *rc = -ENOMEM; kfree(hp_errlog_copy); - complete(hotplug_done); } } @@ -435,6 +655,8 @@ static int dlpar_parse_resource(char **cmd, struct pseries_hp_errorlog *hp_elog) hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_MEM; } else if (sysfs_streq(arg, "cpu")) { hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_CPU; + } else if (sysfs_streq(arg, "dt")) { + hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_DT; } else { pr_err("Invalid resource specified.\n"); return -EINVAL; @@ -534,47 +756,38 @@ static int dlpar_parse_id_type(char **cmd, struct pseries_hp_errorlog *hp_elog) return 0; } -static ssize_t dlpar_store(struct class *class, struct class_attribute *attr, +static ssize_t dlpar_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { - struct pseries_hp_errorlog *hp_elog; - struct completion hotplug_done; + struct pseries_hp_errorlog hp_elog; char *argbuf; char *args; int rc; args = argbuf = kstrdup(buf, GFP_KERNEL); - hp_elog = kzalloc(sizeof(*hp_elog), GFP_KERNEL); - if (!hp_elog || !argbuf) { - pr_info("Could not allocate resources for DLPAR operation\n"); - kfree(argbuf); - kfree(hp_elog); + if (!argbuf) return -ENOMEM; - } /* * Parse out the request from the user, this will be in the form: * <resource> <action> <id_type> <id> */ - rc = dlpar_parse_resource(&args, hp_elog); + rc = dlpar_parse_resource(&args, &hp_elog); if (rc) goto dlpar_store_out; - rc = dlpar_parse_action(&args, hp_elog); + rc = dlpar_parse_action(&args, &hp_elog); if (rc) goto dlpar_store_out; - rc = dlpar_parse_id_type(&args, hp_elog); + rc = dlpar_parse_id_type(&args, &hp_elog); if (rc) goto dlpar_store_out; - init_completion(&hotplug_done); - queue_hotplug_event(hp_elog, &hotplug_done, &rc); - wait_for_completion(&hotplug_done); + rc = handle_dlpar_errorlog(&hp_elog); dlpar_store_out: kfree(argbuf); - kfree(hp_elog); if (rc) pr_err("Could not handle DLPAR request \"%s\"\n", buf); @@ -582,19 +795,33 @@ dlpar_store_out: return rc ? rc : count; } -static ssize_t dlpar_show(struct class *class, struct class_attribute *attr, +static ssize_t dlpar_show(const struct class *class, const struct class_attribute *attr, char *buf) { - return sprintf(buf, "%s\n", "memory,cpu"); + return sprintf(buf, "%s\n", "memory,cpu,dt"); } static CLASS_ATTR_RW(dlpar); -static int __init pseries_dlpar_init(void) +int __init dlpar_workqueue_init(void) { - pseries_hp_wq = alloc_workqueue("pseries hotplug workqueue", - WQ_UNBOUND, 1); + if (pseries_hp_wq) + return 0; + + pseries_hp_wq = alloc_ordered_workqueue("pseries hotplug workqueue", 0); + + return pseries_hp_wq ? 0 : -ENOMEM; +} + +static int __init dlpar_sysfs_init(void) +{ + int rc; + + rc = dlpar_workqueue_init(); + if (rc) + return rc; + return sysfs_create_file(kernel_kobj, &class_attr_dlpar.attr); } -machine_device_initcall(pseries, pseries_dlpar_init); +machine_device_initcall(pseries, dlpar_sysfs_init); |
