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