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