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