Path: blob/master/arch/powerpc/platforms/pseries/dlpar.c
26481 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Support for dynamic reconfiguration for PCI, Memory, and CPU3* Hotplug and Dynamic Logical Partitioning on RPA platforms.4*5* Copyright (C) 2009 Nathan Fontenot6* Copyright (C) 2009 IBM Corporation7*/89#define pr_fmt(fmt) "dlpar: " fmt1011#include <linux/kernel.h>12#include <linux/notifier.h>13#include <linux/spinlock.h>14#include <linux/cpu.h>15#include <linux/slab.h>16#include <linux/of.h>1718#include "of_helpers.h"19#include "pseries.h"2021#include <asm/machdep.h>22#include <linux/uaccess.h>23#include <asm/rtas.h>24#include <asm/rtas-work-area.h>25#include <asm/prom.h>2627static struct workqueue_struct *pseries_hp_wq;2829struct pseries_hp_work {30struct work_struct work;31struct pseries_hp_errorlog *errlog;32};3334struct cc_workarea {35__be32 drc_index;36__be32 zero;37__be32 name_offset;38__be32 prop_length;39__be32 prop_offset;40};4142void dlpar_free_cc_property(struct property *prop)43{44kfree(prop->name);45kfree(prop->value);46kfree(prop);47}4849static struct property *dlpar_parse_cc_property(struct cc_workarea *ccwa)50{51struct property *prop;52char *name;53char *value;5455prop = kzalloc(sizeof(*prop), GFP_KERNEL);56if (!prop)57return NULL;5859name = (char *)ccwa + be32_to_cpu(ccwa->name_offset);60prop->name = kstrdup(name, GFP_KERNEL);61if (!prop->name) {62dlpar_free_cc_property(prop);63return NULL;64}6566prop->length = be32_to_cpu(ccwa->prop_length);67value = (char *)ccwa + be32_to_cpu(ccwa->prop_offset);68prop->value = kmemdup(value, prop->length, GFP_KERNEL);69if (!prop->value) {70dlpar_free_cc_property(prop);71return NULL;72}7374return prop;75}7677static struct device_node *dlpar_parse_cc_node(struct cc_workarea *ccwa)78{79struct device_node *dn;80const char *name;8182dn = kzalloc(sizeof(*dn), GFP_KERNEL);83if (!dn)84return NULL;8586name = (const char *)ccwa + be32_to_cpu(ccwa->name_offset);87dn->full_name = kstrdup(name, GFP_KERNEL);88if (!dn->full_name) {89kfree(dn);90return NULL;91}9293of_node_set_flag(dn, OF_DYNAMIC);94of_node_init(dn);9596return dn;97}9899static void dlpar_free_one_cc_node(struct device_node *dn)100{101struct property *prop;102103while (dn->properties) {104prop = dn->properties;105dn->properties = prop->next;106dlpar_free_cc_property(prop);107}108109kfree(dn->full_name);110kfree(dn);111}112113void dlpar_free_cc_nodes(struct device_node *dn)114{115if (dn->child)116dlpar_free_cc_nodes(dn->child);117118if (dn->sibling)119dlpar_free_cc_nodes(dn->sibling);120121dlpar_free_one_cc_node(dn);122}123124#define COMPLETE 0125#define NEXT_SIBLING 1126#define NEXT_CHILD 2127#define NEXT_PROPERTY 3128#define PREV_PARENT 4129#define MORE_MEMORY 5130#define ERR_CFG_USE -9003131132struct device_node *dlpar_configure_connector(__be32 drc_index,133struct device_node *parent)134{135struct device_node *dn;136struct device_node *first_dn = NULL;137struct device_node *last_dn = NULL;138struct property *property;139struct property *last_property = NULL;140struct cc_workarea *ccwa;141struct rtas_work_area *work_area;142char *data_buf;143int cc_token;144int rc = -1;145146cc_token = rtas_function_token(RTAS_FN_IBM_CONFIGURE_CONNECTOR);147if (cc_token == RTAS_UNKNOWN_SERVICE)148return NULL;149150work_area = rtas_work_area_alloc(SZ_4K);151data_buf = rtas_work_area_raw_buf(work_area);152153ccwa = (struct cc_workarea *)&data_buf[0];154ccwa->drc_index = drc_index;155ccwa->zero = 0;156157do {158do {159rc = rtas_call(cc_token, 2, 1, NULL,160rtas_work_area_phys(work_area), NULL);161} while (rtas_busy_delay(rc));162163switch (rc) {164case COMPLETE:165break;166167case NEXT_SIBLING:168dn = dlpar_parse_cc_node(ccwa);169if (!dn)170goto cc_error;171172dn->parent = last_dn->parent;173last_dn->sibling = dn;174last_dn = dn;175break;176177case NEXT_CHILD:178dn = dlpar_parse_cc_node(ccwa);179if (!dn)180goto cc_error;181182if (!first_dn) {183dn->parent = parent;184first_dn = dn;185} else {186dn->parent = last_dn;187if (last_dn)188last_dn->child = dn;189}190191last_dn = dn;192break;193194case NEXT_PROPERTY:195property = dlpar_parse_cc_property(ccwa);196if (!property)197goto cc_error;198199if (!last_dn->properties)200last_dn->properties = property;201else202last_property->next = property;203204last_property = property;205break;206207case PREV_PARENT:208last_dn = last_dn->parent;209break;210211case MORE_MEMORY:212case ERR_CFG_USE:213default:214printk(KERN_ERR "Unexpected Error (%d) "215"returned from configure-connector\n", rc);216goto cc_error;217}218} while (rc);219220cc_error:221rtas_work_area_free(work_area);222223if (rc) {224if (first_dn)225dlpar_free_cc_nodes(first_dn);226227return NULL;228}229230return first_dn;231}232233int dlpar_attach_node(struct device_node *dn, struct device_node *parent)234{235int rc;236237dn->parent = parent;238239rc = of_attach_node(dn);240if (rc) {241printk(KERN_ERR "Failed to add device node %pOF\n", dn);242return rc;243}244245return 0;246}247248int dlpar_detach_node(struct device_node *dn)249{250struct device_node *child;251int rc;252253for_each_child_of_node(dn, child)254dlpar_detach_node(child);255256rc = of_detach_node(dn);257if (rc)258return rc;259260of_node_put(dn);261262return 0;263}264static int dlpar_changeset_attach_cc_nodes(struct of_changeset *ocs,265struct device_node *dn)266{267int rc;268269rc = of_changeset_attach_node(ocs, dn);270271if (!rc && dn->child)272rc = dlpar_changeset_attach_cc_nodes(ocs, dn->child);273if (!rc && dn->sibling)274rc = dlpar_changeset_attach_cc_nodes(ocs, dn->sibling);275276return rc;277}278279#define DR_ENTITY_SENSE 9003280#define DR_ENTITY_PRESENT 1281#define DR_ENTITY_UNUSABLE 2282#define ALLOCATION_STATE 9003283#define ALLOC_UNUSABLE 0284#define ALLOC_USABLE 1285#define ISOLATION_STATE 9001286#define ISOLATE 0287#define UNISOLATE 1288289int dlpar_acquire_drc(u32 drc_index)290{291int dr_status, rc;292293rc = rtas_get_sensor(DR_ENTITY_SENSE, drc_index, &dr_status);294if (rc || dr_status != DR_ENTITY_UNUSABLE)295return -1;296297rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_USABLE);298if (rc)299return rc;300301rc = rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);302if (rc) {303rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);304return rc;305}306307return 0;308}309310int dlpar_release_drc(u32 drc_index)311{312int dr_status, rc;313314rc = rtas_get_sensor(DR_ENTITY_SENSE, drc_index, &dr_status);315if (rc || dr_status != DR_ENTITY_PRESENT)316return -1;317318rc = rtas_set_indicator(ISOLATION_STATE, drc_index, ISOLATE);319if (rc)320return rc;321322rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);323if (rc) {324rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);325return rc;326}327328return 0;329}330331int dlpar_unisolate_drc(u32 drc_index)332{333int dr_status, rc;334335rc = rtas_get_sensor(DR_ENTITY_SENSE, drc_index, &dr_status);336if (rc || dr_status != DR_ENTITY_PRESENT)337return -1;338339rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);340341return 0;342}343344static struct device_node *345get_device_node_with_drc_index(u32 index)346{347struct device_node *np = NULL;348u32 node_index;349int rc;350351for_each_node_with_property(np, "ibm,my-drc-index") {352rc = of_property_read_u32(np, "ibm,my-drc-index",353&node_index);354if (rc) {355pr_err("%s: %pOF: of_property_read_u32 %s: %d\n",356__func__, np, "ibm,my-drc-index", rc);357of_node_put(np);358return NULL;359}360361if (index == node_index)362break;363}364365return np;366}367368static struct device_node *369get_device_node_with_drc_info(u32 index)370{371struct device_node *np = NULL;372struct of_drc_info drc;373struct property *info;374const __be32 *value;375u32 node_index;376int i, j, count;377378for_each_node_with_property(np, "ibm,drc-info") {379info = of_find_property(np, "ibm,drc-info", NULL);380if (info == NULL) {381/* XXX can this happen? */382of_node_put(np);383return NULL;384}385value = of_prop_next_u32(info, NULL, &count);386if (value == NULL)387continue;388value++;389for (i = 0; i < count; i++) {390if (of_read_drc_info_cell(&info, &value, &drc))391break;392if (index > drc.last_drc_index)393continue;394node_index = drc.drc_index_start;395for (j = 0; j < drc.num_sequential_elems; j++) {396if (index == node_index)397return np;398node_index += drc.sequential_inc;399}400}401}402403return NULL;404}405406static struct device_node *407get_device_node_with_drc_indexes(u32 drc_index)408{409struct device_node *np = NULL;410u32 nr_indexes, index;411int i, rc;412413for_each_node_with_property(np, "ibm,drc-indexes") {414/*415* First element in the array is the total number of416* DRC indexes returned.417*/418rc = of_property_read_u32_index(np, "ibm,drc-indexes",4190, &nr_indexes);420if (rc)421goto out_put_np;422423/*424* Retrieve DRC index from the list and return the425* device node if matched with the specified index.426*/427for (i = 0; i < nr_indexes; i++) {428rc = of_property_read_u32_index(np, "ibm,drc-indexes",429i+1, &index);430if (rc)431goto out_put_np;432433if (drc_index == index)434return np;435}436}437438return NULL;439440out_put_np:441of_node_put(np);442return NULL;443}444445static int dlpar_hp_dt_add(u32 index)446{447struct device_node *np, *nodes;448struct of_changeset ocs;449int rc;450451/*452* Do not add device node(s) if already exists in the453* device tree.454*/455np = get_device_node_with_drc_index(index);456if (np) {457pr_err("%s: Adding device node for index (%d), but "458"already exists in the device tree\n",459__func__, index);460rc = -EINVAL;461goto out;462}463464/*465* Recent FW provides ibm,drc-info property. So search466* for the user specified DRC index from ibm,drc-info467* property. If this property is not available, search468* in the indexes array from ibm,drc-indexes property.469*/470np = get_device_node_with_drc_info(index);471472if (!np) {473np = get_device_node_with_drc_indexes(index);474if (!np)475return -EIO;476}477478/* Next, configure the connector. */479nodes = dlpar_configure_connector(cpu_to_be32(index), np);480if (!nodes) {481rc = -EIO;482goto out;483}484485/*486* Add the new nodes from dlpar_configure_connector() onto487* the device-tree.488*/489of_changeset_init(&ocs);490rc = dlpar_changeset_attach_cc_nodes(&ocs, nodes);491492if (!rc)493rc = of_changeset_apply(&ocs);494else495dlpar_free_cc_nodes(nodes);496497of_changeset_destroy(&ocs);498499out:500of_node_put(np);501return rc;502}503504static int changeset_detach_node_recursive(struct of_changeset *ocs,505struct device_node *node)506{507struct device_node *child;508int rc;509510for_each_child_of_node(node, child) {511rc = changeset_detach_node_recursive(ocs, child);512if (rc) {513of_node_put(child);514return rc;515}516}517518return of_changeset_detach_node(ocs, node);519}520521static int dlpar_hp_dt_remove(u32 drc_index)522{523struct device_node *np;524struct of_changeset ocs;525u32 index;526int rc = 0;527528/*529* Prune all nodes with a matching index.530*/531of_changeset_init(&ocs);532533for_each_node_with_property(np, "ibm,my-drc-index") {534rc = of_property_read_u32(np, "ibm,my-drc-index", &index);535if (rc) {536pr_err("%s: %pOF: of_property_read_u32 %s: %d\n",537__func__, np, "ibm,my-drc-index", rc);538of_node_put(np);539goto out;540}541542if (index == drc_index) {543rc = changeset_detach_node_recursive(&ocs, np);544if (rc) {545of_node_put(np);546goto out;547}548}549}550551rc = of_changeset_apply(&ocs);552553out:554of_changeset_destroy(&ocs);555return rc;556}557558static int dlpar_hp_dt(struct pseries_hp_errorlog *phpe)559{560u32 drc_index;561int rc;562563if (phpe->id_type != PSERIES_HP_ELOG_ID_DRC_INDEX)564return -EINVAL;565566drc_index = be32_to_cpu(phpe->_drc_u.drc_index);567568lock_device_hotplug();569570switch (phpe->action) {571case PSERIES_HP_ELOG_ACTION_ADD:572rc = dlpar_hp_dt_add(drc_index);573break;574case PSERIES_HP_ELOG_ACTION_REMOVE:575rc = dlpar_hp_dt_remove(drc_index);576break;577default:578pr_err("Invalid action (%d) specified\n", phpe->action);579rc = -EINVAL;580break;581}582583unlock_device_hotplug();584585return rc;586}587588int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog)589{590int rc;591592switch (hp_elog->resource) {593case PSERIES_HP_ELOG_RESOURCE_MEM:594rc = dlpar_memory(hp_elog);595break;596case PSERIES_HP_ELOG_RESOURCE_CPU:597rc = dlpar_cpu(hp_elog);598break;599case PSERIES_HP_ELOG_RESOURCE_PMEM:600rc = dlpar_hp_pmem(hp_elog);601break;602case PSERIES_HP_ELOG_RESOURCE_DT:603rc = dlpar_hp_dt(hp_elog);604break;605606default:607pr_warn_ratelimited("Invalid resource (%d) specified\n",608hp_elog->resource);609rc = -EINVAL;610}611612return rc;613}614615static void pseries_hp_work_fn(struct work_struct *work)616{617struct pseries_hp_work *hp_work =618container_of(work, struct pseries_hp_work, work);619620handle_dlpar_errorlog(hp_work->errlog);621622kfree(hp_work->errlog);623kfree(work);624}625626void queue_hotplug_event(struct pseries_hp_errorlog *hp_errlog)627{628struct pseries_hp_work *work;629struct pseries_hp_errorlog *hp_errlog_copy;630631hp_errlog_copy = kmemdup(hp_errlog, sizeof(*hp_errlog), GFP_ATOMIC);632if (!hp_errlog_copy)633return;634635work = kmalloc(sizeof(struct pseries_hp_work), GFP_ATOMIC);636if (work) {637INIT_WORK((struct work_struct *)work, pseries_hp_work_fn);638work->errlog = hp_errlog_copy;639queue_work(pseries_hp_wq, (struct work_struct *)work);640} else {641kfree(hp_errlog_copy);642}643}644645static int dlpar_parse_resource(char **cmd, struct pseries_hp_errorlog *hp_elog)646{647char *arg;648649arg = strsep(cmd, " ");650if (!arg)651return -EINVAL;652653if (sysfs_streq(arg, "memory")) {654hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_MEM;655} else if (sysfs_streq(arg, "cpu")) {656hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_CPU;657} else if (sysfs_streq(arg, "dt")) {658hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_DT;659} else {660pr_err("Invalid resource specified.\n");661return -EINVAL;662}663664return 0;665}666667static int dlpar_parse_action(char **cmd, struct pseries_hp_errorlog *hp_elog)668{669char *arg;670671arg = strsep(cmd, " ");672if (!arg)673return -EINVAL;674675if (sysfs_streq(arg, "add")) {676hp_elog->action = PSERIES_HP_ELOG_ACTION_ADD;677} else if (sysfs_streq(arg, "remove")) {678hp_elog->action = PSERIES_HP_ELOG_ACTION_REMOVE;679} else {680pr_err("Invalid action specified.\n");681return -EINVAL;682}683684return 0;685}686687static int dlpar_parse_id_type(char **cmd, struct pseries_hp_errorlog *hp_elog)688{689char *arg;690u32 count, index;691692arg = strsep(cmd, " ");693if (!arg)694return -EINVAL;695696if (sysfs_streq(arg, "indexed-count")) {697hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_IC;698arg = strsep(cmd, " ");699if (!arg) {700pr_err("No DRC count specified.\n");701return -EINVAL;702}703704if (kstrtou32(arg, 0, &count)) {705pr_err("Invalid DRC count specified.\n");706return -EINVAL;707}708709arg = strsep(cmd, " ");710if (!arg) {711pr_err("No DRC Index specified.\n");712return -EINVAL;713}714715if (kstrtou32(arg, 0, &index)) {716pr_err("Invalid DRC Index specified.\n");717return -EINVAL;718}719720hp_elog->_drc_u.ic.count = cpu_to_be32(count);721hp_elog->_drc_u.ic.index = cpu_to_be32(index);722} else if (sysfs_streq(arg, "index")) {723hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_INDEX;724arg = strsep(cmd, " ");725if (!arg) {726pr_err("No DRC Index specified.\n");727return -EINVAL;728}729730if (kstrtou32(arg, 0, &index)) {731pr_err("Invalid DRC Index specified.\n");732return -EINVAL;733}734735hp_elog->_drc_u.drc_index = cpu_to_be32(index);736} else if (sysfs_streq(arg, "count")) {737hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_COUNT;738arg = strsep(cmd, " ");739if (!arg) {740pr_err("No DRC count specified.\n");741return -EINVAL;742}743744if (kstrtou32(arg, 0, &count)) {745pr_err("Invalid DRC count specified.\n");746return -EINVAL;747}748749hp_elog->_drc_u.drc_count = cpu_to_be32(count);750} else {751pr_err("Invalid id_type specified.\n");752return -EINVAL;753}754755return 0;756}757758static ssize_t dlpar_store(const struct class *class, const struct class_attribute *attr,759const char *buf, size_t count)760{761struct pseries_hp_errorlog hp_elog;762char *argbuf;763char *args;764int rc;765766args = argbuf = kstrdup(buf, GFP_KERNEL);767if (!argbuf)768return -ENOMEM;769770/*771* Parse out the request from the user, this will be in the form:772* <resource> <action> <id_type> <id>773*/774rc = dlpar_parse_resource(&args, &hp_elog);775if (rc)776goto dlpar_store_out;777778rc = dlpar_parse_action(&args, &hp_elog);779if (rc)780goto dlpar_store_out;781782rc = dlpar_parse_id_type(&args, &hp_elog);783if (rc)784goto dlpar_store_out;785786rc = handle_dlpar_errorlog(&hp_elog);787788dlpar_store_out:789kfree(argbuf);790791if (rc)792pr_err("Could not handle DLPAR request \"%s\"\n", buf);793794return rc ? rc : count;795}796797static ssize_t dlpar_show(const struct class *class, const struct class_attribute *attr,798char *buf)799{800return sprintf(buf, "%s\n", "memory,cpu,dt");801}802803static CLASS_ATTR_RW(dlpar);804805int __init dlpar_workqueue_init(void)806{807if (pseries_hp_wq)808return 0;809810pseries_hp_wq = alloc_ordered_workqueue("pseries hotplug workqueue", 0);811812return pseries_hp_wq ? 0 : -ENOMEM;813}814815static int __init dlpar_sysfs_init(void)816{817int rc;818819rc = dlpar_workqueue_init();820if (rc)821return rc;822823return sysfs_create_file(kernel_kobj, &class_attr_dlpar.attr);824}825machine_device_initcall(pseries, dlpar_sysfs_init);826827828829