linux/arch/powerpc/platforms/pseries/hotplug-memory.c
<<
>>
Prefs
   1/*
   2 * pseries Memory Hotplug infrastructure.
   3 *
   4 * Copyright (C) 2008 Badari Pulavarty, IBM Corporation
   5 *
   6 *      This program is free software; you can redistribute it and/or
   7 *      modify it under the terms of the GNU General Public License
   8 *      as published by the Free Software Foundation; either version
   9 *      2 of the License, or (at your option) any later version.
  10 */
  11
  12#include <linux/of.h>
  13#include <linux/of_address.h>
  14#include <linux/memblock.h>
  15#include <linux/vmalloc.h>
  16#include <linux/memory.h>
  17
  18#include <asm/firmware.h>
  19#include <asm/machdep.h>
  20#include <asm/prom.h>
  21#include <asm/sparsemem.h>
  22
  23static unsigned long get_memblock_size(void)
  24{
  25        struct device_node *np;
  26        unsigned int memblock_size = MIN_MEMORY_BLOCK_SIZE;
  27        struct resource r;
  28
  29        np = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
  30        if (np) {
  31                const __be64 *size;
  32
  33                size = of_get_property(np, "ibm,lmb-size", NULL);
  34                if (size)
  35                        memblock_size = be64_to_cpup(size);
  36                of_node_put(np);
  37        } else  if (machine_is(pseries)) {
  38                /* This fallback really only applies to pseries */
  39                unsigned int memzero_size = 0;
  40
  41                np = of_find_node_by_path("/memory@0");
  42                if (np) {
  43                        if (!of_address_to_resource(np, 0, &r))
  44                                memzero_size = resource_size(&r);
  45                        of_node_put(np);
  46                }
  47
  48                if (memzero_size) {
  49                        /* We now know the size of memory@0, use this to find
  50                         * the first memoryblock and get its size.
  51                         */
  52                        char buf[64];
  53
  54                        sprintf(buf, "/memory@%x", memzero_size);
  55                        np = of_find_node_by_path(buf);
  56                        if (np) {
  57                                if (!of_address_to_resource(np, 0, &r))
  58                                        memblock_size = resource_size(&r);
  59                                of_node_put(np);
  60                        }
  61                }
  62        }
  63        return memblock_size;
  64}
  65
  66/* WARNING: This is going to override the generic definition whenever
  67 * pseries is built-in regardless of what platform is active at boot
  68 * time. This is fine for now as this is the only "option" and it
  69 * should work everywhere. If not, we'll have to turn this into a
  70 * ppc_md. callback
  71 */
  72unsigned long memory_block_size_bytes(void)
  73{
  74        return get_memblock_size();
  75}
  76
  77#ifdef CONFIG_MEMORY_HOTREMOVE
  78static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size)
  79{
  80        unsigned long start, start_pfn;
  81        struct zone *zone;
  82        int ret;
  83        unsigned long section;
  84        unsigned long sections_to_remove;
  85
  86        start_pfn = base >> PAGE_SHIFT;
  87
  88        if (!pfn_valid(start_pfn)) {
  89                memblock_remove(base, memblock_size);
  90                return 0;
  91        }
  92
  93        zone = page_zone(pfn_to_page(start_pfn));
  94
  95        /*
  96         * Remove section mappings and sysfs entries for the
  97         * section of the memory we are removing.
  98         *
  99         * NOTE: Ideally, this should be done in generic code like
 100         * remove_memory(). But remove_memory() gets called by writing
 101         * to sysfs "state" file and we can't remove sysfs entries
 102         * while writing to it. So we have to defer it to here.
 103         */
 104        sections_to_remove = (memblock_size >> PAGE_SHIFT) / PAGES_PER_SECTION;
 105        for (section = 0; section < sections_to_remove; section++) {
 106                unsigned long pfn = start_pfn + section * PAGES_PER_SECTION;
 107                ret = __remove_pages(zone, pfn, PAGES_PER_SECTION);
 108                if (ret)
 109                        return ret;
 110        }
 111
 112        /*
 113         * Update memory regions for memory remove
 114         */
 115        memblock_remove(base, memblock_size);
 116
 117        /*
 118         * Remove htab bolted mappings for this section of memory
 119         */
 120        start = (unsigned long)__va(base);
 121        ret = remove_section_mapping(start, start + memblock_size);
 122
 123        /* Ensure all vmalloc mappings are flushed in case they also
 124         * hit that section of memory
 125         */
 126        vm_unmap_aliases();
 127
 128        return ret;
 129}
 130
 131static int pseries_remove_memory(struct device_node *np)
 132{
 133        const char *type;
 134        const unsigned int *regs;
 135        unsigned long base;
 136        unsigned int lmb_size;
 137        int ret = -EINVAL;
 138
 139        /*
 140         * Check to see if we are actually removing memory
 141         */
 142        type = of_get_property(np, "device_type", NULL);
 143        if (type == NULL || strcmp(type, "memory") != 0)
 144                return 0;
 145
 146        /*
 147         * Find the bae address and size of the memblock
 148         */
 149        regs = of_get_property(np, "reg", NULL);
 150        if (!regs)
 151                return ret;
 152
 153        base = *(unsigned long *)regs;
 154        lmb_size = regs[3];
 155
 156        ret = pseries_remove_memblock(base, lmb_size);
 157        return ret;
 158}
 159#else
 160static inline int pseries_remove_memblock(unsigned long base,
 161                                          unsigned int memblock_size)
 162{
 163        return -EOPNOTSUPP;
 164}
 165static inline int pseries_remove_memory(struct device_node *np)
 166{
 167        return -EOPNOTSUPP;
 168}
 169#endif /* CONFIG_MEMORY_HOTREMOVE */
 170
 171static int pseries_add_memory(struct device_node *np)
 172{
 173        const char *type;
 174        const unsigned int *regs;
 175        unsigned long base;
 176        unsigned int lmb_size;
 177        int ret = -EINVAL;
 178
 179        /*
 180         * Check to see if we are actually adding memory
 181         */
 182        type = of_get_property(np, "device_type", NULL);
 183        if (type == NULL || strcmp(type, "memory") != 0)
 184                return 0;
 185
 186        /*
 187         * Find the base and size of the memblock
 188         */
 189        regs = of_get_property(np, "reg", NULL);
 190        if (!regs)
 191                return ret;
 192
 193        base = *(unsigned long *)regs;
 194        lmb_size = regs[3];
 195
 196        /*
 197         * Update memory region to represent the memory add
 198         */
 199        ret = memblock_add(base, lmb_size);
 200        return (ret < 0) ? -EINVAL : 0;
 201}
 202
 203static int pseries_update_drconf_memory(struct of_prop_reconfig *pr)
 204{
 205        struct of_drconf_cell *new_drmem, *old_drmem;
 206        unsigned long memblock_size;
 207        u32 entries;
 208        u32 *p;
 209        int i, rc = -EINVAL;
 210
 211        memblock_size = get_memblock_size();
 212        if (!memblock_size)
 213                return -EINVAL;
 214
 215        p = (u32 *)of_get_property(pr->dn, "ibm,dynamic-memory", NULL);
 216        if (!p)
 217                return -EINVAL;
 218
 219        /* The first int of the property is the number of lmb's described
 220         * by the property. This is followed by an array of of_drconf_cell
 221         * entries. Get the niumber of entries and skip to the array of
 222         * of_drconf_cell's.
 223         */
 224        entries = *p++;
 225        old_drmem = (struct of_drconf_cell *)p;
 226
 227        p = (u32 *)pr->prop->value;
 228        p++;
 229        new_drmem = (struct of_drconf_cell *)p;
 230
 231        for (i = 0; i < entries; i++) {
 232                if ((old_drmem[i].flags & DRCONF_MEM_ASSIGNED) &&
 233                    (!(new_drmem[i].flags & DRCONF_MEM_ASSIGNED))) {
 234                        rc = pseries_remove_memblock(old_drmem[i].base_addr,
 235                                                     memblock_size);
 236                        break;
 237                } else if ((!(old_drmem[i].flags & DRCONF_MEM_ASSIGNED)) &&
 238                           (new_drmem[i].flags & DRCONF_MEM_ASSIGNED)) {
 239                        rc = memblock_add(old_drmem[i].base_addr,
 240                                          memblock_size);
 241                        rc = (rc < 0) ? -EINVAL : 0;
 242                        break;
 243                }
 244        }
 245
 246        return rc;
 247}
 248
 249static int pseries_memory_notifier(struct notifier_block *nb,
 250                                   unsigned long action, void *node)
 251{
 252        struct of_prop_reconfig *pr;
 253        int err = 0;
 254
 255        switch (action) {
 256        case OF_RECONFIG_ATTACH_NODE:
 257                err = pseries_add_memory(node);
 258                break;
 259        case OF_RECONFIG_DETACH_NODE:
 260                err = pseries_remove_memory(node);
 261                break;
 262        case OF_RECONFIG_UPDATE_PROPERTY:
 263                pr = (struct of_prop_reconfig *)node;
 264                if (!strcmp(pr->prop->name, "ibm,dynamic-memory"))
 265                        err = pseries_update_drconf_memory(pr);
 266                break;
 267        }
 268        return notifier_from_errno(err);
 269}
 270
 271static struct notifier_block pseries_mem_nb = {
 272        .notifier_call = pseries_memory_notifier,
 273};
 274
 275static int __init pseries_memory_hotplug_init(void)
 276{
 277        if (firmware_has_feature(FW_FEATURE_LPAR))
 278                of_reconfig_notifier_register(&pseries_mem_nb);
 279
 280        return 0;
 281}
 282machine_device_initcall(pseries, pseries_memory_hotplug_init);
 283