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