Path: blob/master/arch/powerpc/platforms/pseries/hotplug-memory.c
26481 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* pseries Memory Hotplug infrastructure.3*4* Copyright (C) 2008 Badari Pulavarty, IBM Corporation5*/67#define pr_fmt(fmt) "pseries-hotplug-mem: " fmt89#include <linux/of.h>10#include <linux/of_address.h>11#include <linux/memblock.h>12#include <linux/memory.h>13#include <linux/memory_hotplug.h>14#include <linux/slab.h>1516#include <asm/firmware.h>17#include <asm/machdep.h>18#include <asm/sparsemem.h>19#include <asm/fadump.h>20#include <asm/drmem.h>21#include "pseries.h"2223static void dlpar_free_property(struct property *prop)24{25kfree(prop->name);26kfree(prop->value);27kfree(prop);28}2930static struct property *dlpar_clone_property(struct property *prop,31u32 prop_size)32{33struct property *new_prop;3435new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);36if (!new_prop)37return NULL;3839new_prop->name = kstrdup(prop->name, GFP_KERNEL);40new_prop->value = kzalloc(prop_size, GFP_KERNEL);41if (!new_prop->name || !new_prop->value) {42dlpar_free_property(new_prop);43return NULL;44}4546memcpy(new_prop->value, prop->value, prop->length);47new_prop->length = prop_size;4849of_property_set_flag(new_prop, OF_DYNAMIC);50return new_prop;51}5253static bool find_aa_index(struct device_node *dr_node,54struct property *ala_prop,55const u32 *lmb_assoc, u32 *aa_index)56{57__be32 *assoc_arrays;58u32 new_prop_size;59struct property *new_prop;60int aa_arrays, aa_array_entries, aa_array_sz;61int i, index;6263/*64* The ibm,associativity-lookup-arrays property is defined to be65* a 32-bit value specifying the number of associativity arrays66* followed by a 32-bitvalue specifying the number of entries per67* array, followed by the associativity arrays.68*/69assoc_arrays = ala_prop->value;7071aa_arrays = be32_to_cpu(assoc_arrays[0]);72aa_array_entries = be32_to_cpu(assoc_arrays[1]);73aa_array_sz = aa_array_entries * sizeof(u32);7475for (i = 0; i < aa_arrays; i++) {76index = (i * aa_array_entries) + 2;7778if (memcmp(&assoc_arrays[index], &lmb_assoc[1], aa_array_sz))79continue;8081*aa_index = i;82return true;83}8485new_prop_size = ala_prop->length + aa_array_sz;86new_prop = dlpar_clone_property(ala_prop, new_prop_size);87if (!new_prop)88return false;8990assoc_arrays = new_prop->value;9192/* increment the number of entries in the lookup array */93assoc_arrays[0] = cpu_to_be32(aa_arrays + 1);9495/* copy the new associativity into the lookup array */96index = aa_arrays * aa_array_entries + 2;97memcpy(&assoc_arrays[index], &lmb_assoc[1], aa_array_sz);9899of_update_property(dr_node, new_prop);100101/*102* The associativity lookup array index for this lmb is103* number of entries - 1 since we added its associativity104* to the end of the lookup array.105*/106*aa_index = be32_to_cpu(assoc_arrays[0]) - 1;107return true;108}109110static int update_lmb_associativity_index(struct drmem_lmb *lmb)111{112struct device_node *parent, *lmb_node, *dr_node;113struct property *ala_prop;114const u32 *lmb_assoc;115u32 aa_index;116bool found;117118parent = of_find_node_by_path("/");119if (!parent)120return -ENODEV;121122lmb_node = dlpar_configure_connector(cpu_to_be32(lmb->drc_index),123parent);124of_node_put(parent);125if (!lmb_node)126return -EINVAL;127128lmb_assoc = of_get_property(lmb_node, "ibm,associativity", NULL);129if (!lmb_assoc) {130dlpar_free_cc_nodes(lmb_node);131return -ENODEV;132}133134update_numa_distance(lmb_node);135136dr_node = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");137if (!dr_node) {138dlpar_free_cc_nodes(lmb_node);139return -ENODEV;140}141142ala_prop = of_find_property(dr_node, "ibm,associativity-lookup-arrays",143NULL);144if (!ala_prop) {145of_node_put(dr_node);146dlpar_free_cc_nodes(lmb_node);147return -ENODEV;148}149150found = find_aa_index(dr_node, ala_prop, lmb_assoc, &aa_index);151152of_node_put(dr_node);153dlpar_free_cc_nodes(lmb_node);154155if (!found) {156pr_err("Could not find LMB associativity\n");157return -1;158}159160lmb->aa_index = aa_index;161return 0;162}163164static struct memory_block *lmb_to_memblock(struct drmem_lmb *lmb)165{166unsigned long section_nr;167struct memory_block *mem_block;168169section_nr = pfn_to_section_nr(PFN_DOWN(lmb->base_addr));170171mem_block = find_memory_block(section_nr);172return mem_block;173}174175static int get_lmb_range(u32 drc_index, int n_lmbs,176struct drmem_lmb **start_lmb,177struct drmem_lmb **end_lmb)178{179struct drmem_lmb *lmb, *start, *end;180struct drmem_lmb *limit;181182start = NULL;183for_each_drmem_lmb(lmb) {184if (lmb->drc_index == drc_index) {185start = lmb;186break;187}188}189190if (!start)191return -EINVAL;192193end = &start[n_lmbs];194195limit = &drmem_info->lmbs[drmem_info->n_lmbs];196if (end > limit)197return -EINVAL;198199*start_lmb = start;200*end_lmb = end;201return 0;202}203204static int dlpar_change_lmb_state(struct drmem_lmb *lmb, bool online)205{206struct memory_block *mem_block;207int rc;208209mem_block = lmb_to_memblock(lmb);210if (!mem_block) {211pr_err("Failed memory block lookup for LMB 0x%x\n", lmb->drc_index);212return -EINVAL;213}214215if (online && mem_block->dev.offline)216rc = device_online(&mem_block->dev);217else if (!online && !mem_block->dev.offline)218rc = device_offline(&mem_block->dev);219else220rc = 0;221222put_device(&mem_block->dev);223224return rc;225}226227static int dlpar_online_lmb(struct drmem_lmb *lmb)228{229return dlpar_change_lmb_state(lmb, true);230}231232#ifdef CONFIG_MEMORY_HOTREMOVE233static int dlpar_offline_lmb(struct drmem_lmb *lmb)234{235return dlpar_change_lmb_state(lmb, false);236}237238static int pseries_remove_memblock(unsigned long base, unsigned long memblock_size)239{240unsigned long start_pfn;241int sections_per_block;242int i;243244start_pfn = base >> PAGE_SHIFT;245246lock_device_hotplug();247248if (!pfn_valid(start_pfn))249goto out;250251sections_per_block = memory_block_size / MIN_MEMORY_BLOCK_SIZE;252253for (i = 0; i < sections_per_block; i++) {254__remove_memory(base, MIN_MEMORY_BLOCK_SIZE);255base += MIN_MEMORY_BLOCK_SIZE;256}257258out:259/* Update memory regions for memory remove */260memblock_remove(base, memblock_size);261unlock_device_hotplug();262return 0;263}264265static int pseries_remove_mem_node(struct device_node *np)266{267int ret;268struct resource res;269270/*271* Check to see if we are actually removing memory272*/273if (!of_node_is_type(np, "memory"))274return 0;275276/*277* Find the base address and size of the memblock278*/279ret = of_address_to_resource(np, 0, &res);280if (ret)281return ret;282283pseries_remove_memblock(res.start, resource_size(&res));284return 0;285}286287static bool lmb_is_removable(struct drmem_lmb *lmb)288{289if ((lmb->flags & DRCONF_MEM_RESERVED) ||290!(lmb->flags & DRCONF_MEM_ASSIGNED))291return false;292293#ifdef CONFIG_FA_DUMP294/*295* Don't hot-remove memory that falls in fadump boot memory area296* and memory that is reserved for capturing old kernel memory.297*/298if (is_fadump_memory_area(lmb->base_addr, memory_block_size_bytes()))299return false;300#endif301/* device_offline() will determine if we can actually remove this lmb */302return true;303}304305static int dlpar_add_lmb(struct drmem_lmb *);306307static int dlpar_remove_lmb(struct drmem_lmb *lmb)308{309struct memory_block *mem_block;310int rc;311312if (!lmb_is_removable(lmb))313return -EINVAL;314315mem_block = lmb_to_memblock(lmb);316if (mem_block == NULL)317return -EINVAL;318319rc = dlpar_offline_lmb(lmb);320if (rc) {321put_device(&mem_block->dev);322return rc;323}324325__remove_memory(lmb->base_addr, memory_block_size);326put_device(&mem_block->dev);327328/* Update memory regions for memory remove */329memblock_remove(lmb->base_addr, memory_block_size);330331invalidate_lmb_associativity_index(lmb);332lmb->flags &= ~DRCONF_MEM_ASSIGNED;333334return 0;335}336337static int dlpar_memory_remove_by_count(u32 lmbs_to_remove)338{339struct drmem_lmb *lmb;340int lmbs_reserved = 0;341int lmbs_available = 0;342int rc;343344pr_info("Attempting to hot-remove %d LMB(s)\n", lmbs_to_remove);345346if (lmbs_to_remove == 0)347return -EINVAL;348349/* Validate that there are enough LMBs to satisfy the request */350for_each_drmem_lmb(lmb) {351if (lmb_is_removable(lmb))352lmbs_available++;353354if (lmbs_available == lmbs_to_remove)355break;356}357358if (lmbs_available < lmbs_to_remove) {359pr_info("Not enough LMBs available (%d of %d) to satisfy request\n",360lmbs_available, lmbs_to_remove);361return -EINVAL;362}363364for_each_drmem_lmb(lmb) {365rc = dlpar_remove_lmb(lmb);366if (rc)367continue;368369/* Mark this lmb so we can add it later if all of the370* requested LMBs cannot be removed.371*/372drmem_mark_lmb_reserved(lmb);373374lmbs_reserved++;375if (lmbs_reserved == lmbs_to_remove)376break;377}378379if (lmbs_reserved != lmbs_to_remove) {380pr_err("Memory hot-remove failed, adding LMB's back\n");381382for_each_drmem_lmb(lmb) {383if (!drmem_lmb_reserved(lmb))384continue;385386rc = dlpar_add_lmb(lmb);387if (rc)388pr_err("Failed to add LMB back, drc index %x\n",389lmb->drc_index);390391drmem_remove_lmb_reservation(lmb);392393lmbs_reserved--;394if (lmbs_reserved == 0)395break;396}397398rc = -EINVAL;399} else {400for_each_drmem_lmb(lmb) {401if (!drmem_lmb_reserved(lmb))402continue;403404dlpar_release_drc(lmb->drc_index);405pr_info("Memory at %llx was hot-removed\n",406lmb->base_addr);407408drmem_remove_lmb_reservation(lmb);409410lmbs_reserved--;411if (lmbs_reserved == 0)412break;413}414rc = 0;415}416417return rc;418}419420static int dlpar_memory_remove_by_index(u32 drc_index)421{422struct drmem_lmb *lmb;423int lmb_found;424int rc;425426pr_debug("Attempting to hot-remove LMB, drc index %x\n", drc_index);427428lmb_found = 0;429for_each_drmem_lmb(lmb) {430if (lmb->drc_index == drc_index) {431lmb_found = 1;432rc = dlpar_remove_lmb(lmb);433if (!rc)434dlpar_release_drc(lmb->drc_index);435436break;437}438}439440if (!lmb_found) {441pr_debug("Failed to look up LMB for drc index %x\n", drc_index);442rc = -EINVAL;443} else if (rc) {444pr_debug("Failed to hot-remove memory at %llx\n",445lmb->base_addr);446} else {447pr_debug("Memory at %llx was hot-removed\n", lmb->base_addr);448}449450return rc;451}452453static int dlpar_memory_remove_by_ic(u32 lmbs_to_remove, u32 drc_index)454{455struct drmem_lmb *lmb, *start_lmb, *end_lmb;456int rc;457458pr_info("Attempting to hot-remove %u LMB(s) at %x\n",459lmbs_to_remove, drc_index);460461if (lmbs_to_remove == 0)462return -EINVAL;463464rc = get_lmb_range(drc_index, lmbs_to_remove, &start_lmb, &end_lmb);465if (rc)466return -EINVAL;467468/*469* Validate that all LMBs in range are not reserved. Note that it470* is ok if they are !ASSIGNED since our goal here is to remove the471* LMB range, regardless of whether some LMBs were already removed472* by any other reason.473*474* This is a contrast to what is done in remove_by_count() where we475* check for both RESERVED and !ASSIGNED (via lmb_is_removable()),476* because we want to remove a fixed amount of LMBs in that function.477*/478for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {479if (lmb->flags & DRCONF_MEM_RESERVED) {480pr_err("Memory at %llx (drc index %x) is reserved\n",481lmb->base_addr, lmb->drc_index);482return -EINVAL;483}484}485486for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {487/*488* dlpar_remove_lmb() will error out if the LMB is already489* !ASSIGNED, but this case is a no-op for us.490*/491if (!(lmb->flags & DRCONF_MEM_ASSIGNED))492continue;493494rc = dlpar_remove_lmb(lmb);495if (rc)496break;497498drmem_mark_lmb_reserved(lmb);499}500501if (rc) {502pr_err("Memory indexed-count-remove failed, adding any removed LMBs\n");503504505for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {506if (!drmem_lmb_reserved(lmb))507continue;508509/*510* Setting the isolation state of an UNISOLATED/CONFIGURED511* device to UNISOLATE is a no-op, but the hypervisor can512* use it as a hint that the LMB removal failed.513*/514dlpar_unisolate_drc(lmb->drc_index);515516rc = dlpar_add_lmb(lmb);517if (rc)518pr_err("Failed to add LMB, drc index %x\n",519lmb->drc_index);520521drmem_remove_lmb_reservation(lmb);522}523rc = -EINVAL;524} else {525for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {526if (!drmem_lmb_reserved(lmb))527continue;528529dlpar_release_drc(lmb->drc_index);530pr_info("Memory at %llx (drc index %x) was hot-removed\n",531lmb->base_addr, lmb->drc_index);532533drmem_remove_lmb_reservation(lmb);534}535}536537return rc;538}539540#else541static inline int pseries_remove_memblock(unsigned long base,542unsigned long memblock_size)543{544return -EOPNOTSUPP;545}546static inline int pseries_remove_mem_node(struct device_node *np)547{548return 0;549}550static int dlpar_remove_lmb(struct drmem_lmb *lmb)551{552return -EOPNOTSUPP;553}554static int dlpar_memory_remove_by_count(u32 lmbs_to_remove)555{556return -EOPNOTSUPP;557}558static int dlpar_memory_remove_by_index(u32 drc_index)559{560return -EOPNOTSUPP;561}562563static int dlpar_memory_remove_by_ic(u32 lmbs_to_remove, u32 drc_index)564{565return -EOPNOTSUPP;566}567#endif /* CONFIG_MEMORY_HOTREMOVE */568569static int dlpar_add_lmb(struct drmem_lmb *lmb)570{571unsigned long block_sz;572int nid, rc;573574if (lmb->flags & DRCONF_MEM_ASSIGNED)575return -EINVAL;576577rc = update_lmb_associativity_index(lmb);578if (rc) {579dlpar_release_drc(lmb->drc_index);580pr_err("Failed to configure LMB 0x%x\n", lmb->drc_index);581return rc;582}583584block_sz = memory_block_size_bytes();585586/* Find the node id for this LMB. Fake one if necessary. */587nid = of_drconf_to_nid_single(lmb);588if (nid < 0 || !node_possible(nid))589nid = first_online_node;590591/* Add the memory */592rc = __add_memory(nid, lmb->base_addr, block_sz, MHP_MEMMAP_ON_MEMORY);593if (rc) {594pr_err("Failed to add LMB 0x%x to node %u", lmb->drc_index, nid);595invalidate_lmb_associativity_index(lmb);596return rc;597}598599rc = dlpar_online_lmb(lmb);600if (rc) {601pr_err("Failed to online LMB 0x%x on node %u\n", lmb->drc_index, nid);602__remove_memory(lmb->base_addr, block_sz);603invalidate_lmb_associativity_index(lmb);604} else {605lmb->flags |= DRCONF_MEM_ASSIGNED;606}607608return rc;609}610611static int dlpar_memory_add_by_count(u32 lmbs_to_add)612{613struct drmem_lmb *lmb;614int lmbs_available = 0;615int lmbs_reserved = 0;616int rc;617618pr_info("Attempting to hot-add %d LMB(s)\n", lmbs_to_add);619620if (lmbs_to_add == 0)621return -EINVAL;622623/* Validate that there are enough LMBs to satisfy the request */624for_each_drmem_lmb(lmb) {625if (lmb->flags & DRCONF_MEM_RESERVED)626continue;627628if (!(lmb->flags & DRCONF_MEM_ASSIGNED))629lmbs_available++;630631if (lmbs_available == lmbs_to_add)632break;633}634635if (lmbs_available < lmbs_to_add)636return -EINVAL;637638for_each_drmem_lmb(lmb) {639if (lmb->flags & DRCONF_MEM_ASSIGNED)640continue;641642rc = dlpar_acquire_drc(lmb->drc_index);643if (rc)644continue;645646rc = dlpar_add_lmb(lmb);647if (rc) {648dlpar_release_drc(lmb->drc_index);649continue;650}651652/* Mark this lmb so we can remove it later if all of the653* requested LMBs cannot be added.654*/655drmem_mark_lmb_reserved(lmb);656lmbs_reserved++;657if (lmbs_reserved == lmbs_to_add)658break;659}660661if (lmbs_reserved != lmbs_to_add) {662pr_err("Memory hot-add failed, removing any added LMBs\n");663664for_each_drmem_lmb(lmb) {665if (!drmem_lmb_reserved(lmb))666continue;667668rc = dlpar_remove_lmb(lmb);669if (rc)670pr_err("Failed to remove LMB, drc index %x\n",671lmb->drc_index);672else673dlpar_release_drc(lmb->drc_index);674675drmem_remove_lmb_reservation(lmb);676lmbs_reserved--;677678if (lmbs_reserved == 0)679break;680}681rc = -EINVAL;682} else {683for_each_drmem_lmb(lmb) {684if (!drmem_lmb_reserved(lmb))685continue;686687pr_debug("Memory at %llx (drc index %x) was hot-added\n",688lmb->base_addr, lmb->drc_index);689drmem_remove_lmb_reservation(lmb);690lmbs_reserved--;691692if (lmbs_reserved == 0)693break;694}695rc = 0;696}697698return rc;699}700701static int dlpar_memory_add_by_index(u32 drc_index)702{703struct drmem_lmb *lmb;704int rc, lmb_found;705706pr_info("Attempting to hot-add LMB, drc index %x\n", drc_index);707708lmb_found = 0;709for_each_drmem_lmb(lmb) {710if (lmb->drc_index == drc_index) {711lmb_found = 1;712rc = dlpar_acquire_drc(lmb->drc_index);713if (!rc) {714rc = dlpar_add_lmb(lmb);715if (rc)716dlpar_release_drc(lmb->drc_index);717}718719break;720}721}722723if (!lmb_found)724rc = -EINVAL;725726if (rc)727pr_info("Failed to hot-add memory, drc index %x\n", drc_index);728else729pr_info("Memory at %llx (drc index %x) was hot-added\n",730lmb->base_addr, drc_index);731732return rc;733}734735static int dlpar_memory_add_by_ic(u32 lmbs_to_add, u32 drc_index)736{737struct drmem_lmb *lmb, *start_lmb, *end_lmb;738int rc;739740pr_info("Attempting to hot-add %u LMB(s) at index %x\n",741lmbs_to_add, drc_index);742743if (lmbs_to_add == 0)744return -EINVAL;745746rc = get_lmb_range(drc_index, lmbs_to_add, &start_lmb, &end_lmb);747if (rc)748return -EINVAL;749750/* Validate that the LMBs in this range are not reserved */751for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {752/* Fail immediately if the whole range can't be hot-added */753if (lmb->flags & DRCONF_MEM_RESERVED) {754pr_err("Memory at %llx (drc index %x) is reserved\n",755lmb->base_addr, lmb->drc_index);756return -EINVAL;757}758}759760for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {761if (lmb->flags & DRCONF_MEM_ASSIGNED)762continue;763764rc = dlpar_acquire_drc(lmb->drc_index);765if (rc)766break;767768rc = dlpar_add_lmb(lmb);769if (rc) {770dlpar_release_drc(lmb->drc_index);771break;772}773774drmem_mark_lmb_reserved(lmb);775}776777if (rc) {778pr_err("Memory indexed-count-add failed, removing any added LMBs\n");779780for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {781if (!drmem_lmb_reserved(lmb))782continue;783784rc = dlpar_remove_lmb(lmb);785if (rc)786pr_err("Failed to remove LMB, drc index %x\n",787lmb->drc_index);788else789dlpar_release_drc(lmb->drc_index);790791drmem_remove_lmb_reservation(lmb);792}793rc = -EINVAL;794} else {795for_each_drmem_lmb_in_range(lmb, start_lmb, end_lmb) {796if (!drmem_lmb_reserved(lmb))797continue;798799pr_info("Memory at %llx (drc index %x) was hot-added\n",800lmb->base_addr, lmb->drc_index);801drmem_remove_lmb_reservation(lmb);802}803}804805return rc;806}807808int dlpar_memory(struct pseries_hp_errorlog *hp_elog)809{810u32 count, drc_index;811int rc;812813lock_device_hotplug();814815switch (hp_elog->action) {816case PSERIES_HP_ELOG_ACTION_ADD:817switch (hp_elog->id_type) {818case PSERIES_HP_ELOG_ID_DRC_COUNT:819count = be32_to_cpu(hp_elog->_drc_u.drc_count);820rc = dlpar_memory_add_by_count(count);821break;822case PSERIES_HP_ELOG_ID_DRC_INDEX:823drc_index = be32_to_cpu(hp_elog->_drc_u.drc_index);824rc = dlpar_memory_add_by_index(drc_index);825break;826case PSERIES_HP_ELOG_ID_DRC_IC:827count = be32_to_cpu(hp_elog->_drc_u.ic.count);828drc_index = be32_to_cpu(hp_elog->_drc_u.ic.index);829rc = dlpar_memory_add_by_ic(count, drc_index);830break;831default:832rc = -EINVAL;833break;834}835836break;837case PSERIES_HP_ELOG_ACTION_REMOVE:838switch (hp_elog->id_type) {839case PSERIES_HP_ELOG_ID_DRC_COUNT:840count = be32_to_cpu(hp_elog->_drc_u.drc_count);841rc = dlpar_memory_remove_by_count(count);842break;843case PSERIES_HP_ELOG_ID_DRC_INDEX:844drc_index = be32_to_cpu(hp_elog->_drc_u.drc_index);845rc = dlpar_memory_remove_by_index(drc_index);846break;847case PSERIES_HP_ELOG_ID_DRC_IC:848count = be32_to_cpu(hp_elog->_drc_u.ic.count);849drc_index = be32_to_cpu(hp_elog->_drc_u.ic.index);850rc = dlpar_memory_remove_by_ic(count, drc_index);851break;852default:853rc = -EINVAL;854break;855}856857break;858default:859pr_err("Invalid action (%d) specified\n", hp_elog->action);860rc = -EINVAL;861break;862}863864if (!rc)865rc = drmem_update_dt();866867unlock_device_hotplug();868return rc;869}870871static int pseries_add_mem_node(struct device_node *np)872{873int ret;874struct resource res;875876/*877* Check to see if we are actually adding memory878*/879if (!of_node_is_type(np, "memory"))880return 0;881882/*883* Find the base and size of the memblock884*/885ret = of_address_to_resource(np, 0, &res);886if (ret)887return ret;888889/*890* Update memory region to represent the memory add891*/892ret = memblock_add(res.start, resource_size(&res));893return (ret < 0) ? -EINVAL : 0;894}895896static int pseries_memory_notifier(struct notifier_block *nb,897unsigned long action, void *data)898{899struct of_reconfig_data *rd = data;900int err = 0;901902switch (action) {903case OF_RECONFIG_ATTACH_NODE:904err = pseries_add_mem_node(rd->dn);905break;906case OF_RECONFIG_DETACH_NODE:907err = pseries_remove_mem_node(rd->dn);908break;909case OF_RECONFIG_UPDATE_PROPERTY:910if (!strcmp(rd->dn->name,911"ibm,dynamic-reconfiguration-memory"))912drmem_update_lmbs(rd->prop);913}914return notifier_from_errno(err);915}916917static struct notifier_block pseries_mem_nb = {918.notifier_call = pseries_memory_notifier,919};920921static int __init pseries_memory_hotplug_init(void)922{923if (firmware_has_feature(FW_FEATURE_LPAR))924of_reconfig_notifier_register(&pseries_mem_nb);925926return 0;927}928machine_device_initcall(pseries, pseries_memory_hotplug_init);929930931