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