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