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