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