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