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