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