diff options
Diffstat (limited to 'arch/powerpc/platforms/pseries/mobility.c')
-rw-r--r-- | arch/powerpc/platforms/pseries/mobility.c | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/pseries/mobility.c b/arch/powerpc/platforms/pseries/mobility.c new file mode 100644 index 000000000000..3e7f651e50ac --- /dev/null +++ b/arch/powerpc/platforms/pseries/mobility.c | |||
@@ -0,0 +1,362 @@ | |||
1 | /* | ||
2 | * Support for Partition Mobility/Migration | ||
3 | * | ||
4 | * Copyright (C) 2010 Nathan Fontenot | ||
5 | * Copyright (C) 2010 IBM Corporation | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License version | ||
9 | * 2 as published by the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/kobject.h> | ||
14 | #include <linux/smp.h> | ||
15 | #include <linux/completion.h> | ||
16 | #include <linux/device.h> | ||
17 | #include <linux/delay.h> | ||
18 | #include <linux/slab.h> | ||
19 | |||
20 | #include <asm/rtas.h> | ||
21 | #include "pseries.h" | ||
22 | |||
23 | static struct kobject *mobility_kobj; | ||
24 | |||
25 | struct update_props_workarea { | ||
26 | u32 phandle; | ||
27 | u32 state; | ||
28 | u64 reserved; | ||
29 | u32 nprops; | ||
30 | }; | ||
31 | |||
32 | #define NODE_ACTION_MASK 0xff000000 | ||
33 | #define NODE_COUNT_MASK 0x00ffffff | ||
34 | |||
35 | #define DELETE_DT_NODE 0x01000000 | ||
36 | #define UPDATE_DT_NODE 0x02000000 | ||
37 | #define ADD_DT_NODE 0x03000000 | ||
38 | |||
39 | static int mobility_rtas_call(int token, char *buf) | ||
40 | { | ||
41 | int rc; | ||
42 | |||
43 | spin_lock(&rtas_data_buf_lock); | ||
44 | |||
45 | memcpy(rtas_data_buf, buf, RTAS_DATA_BUF_SIZE); | ||
46 | rc = rtas_call(token, 2, 1, NULL, rtas_data_buf, 1); | ||
47 | memcpy(buf, rtas_data_buf, RTAS_DATA_BUF_SIZE); | ||
48 | |||
49 | spin_unlock(&rtas_data_buf_lock); | ||
50 | return rc; | ||
51 | } | ||
52 | |||
53 | static int delete_dt_node(u32 phandle) | ||
54 | { | ||
55 | struct device_node *dn; | ||
56 | |||
57 | dn = of_find_node_by_phandle(phandle); | ||
58 | if (!dn) | ||
59 | return -ENOENT; | ||
60 | |||
61 | dlpar_detach_node(dn); | ||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | static int update_dt_property(struct device_node *dn, struct property **prop, | ||
66 | const char *name, u32 vd, char *value) | ||
67 | { | ||
68 | struct property *new_prop = *prop; | ||
69 | struct property *old_prop; | ||
70 | int more = 0; | ||
71 | |||
72 | /* A negative 'vd' value indicates that only part of the new property | ||
73 | * value is contained in the buffer and we need to call | ||
74 | * ibm,update-properties again to get the rest of the value. | ||
75 | * | ||
76 | * A negative value is also the two's compliment of the actual value. | ||
77 | */ | ||
78 | if (vd & 0x80000000) { | ||
79 | vd = ~vd + 1; | ||
80 | more = 1; | ||
81 | } | ||
82 | |||
83 | if (new_prop) { | ||
84 | /* partial property fixup */ | ||
85 | char *new_data = kzalloc(new_prop->length + vd, GFP_KERNEL); | ||
86 | if (!new_data) | ||
87 | return -ENOMEM; | ||
88 | |||
89 | memcpy(new_data, new_prop->value, new_prop->length); | ||
90 | memcpy(new_data + new_prop->length, value, vd); | ||
91 | |||
92 | kfree(new_prop->value); | ||
93 | new_prop->value = new_data; | ||
94 | new_prop->length += vd; | ||
95 | } else { | ||
96 | new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); | ||
97 | if (!new_prop) | ||
98 | return -ENOMEM; | ||
99 | |||
100 | new_prop->name = kstrdup(name, GFP_KERNEL); | ||
101 | if (!new_prop->name) { | ||
102 | kfree(new_prop); | ||
103 | return -ENOMEM; | ||
104 | } | ||
105 | |||
106 | new_prop->length = vd; | ||
107 | new_prop->value = kzalloc(new_prop->length, GFP_KERNEL); | ||
108 | if (!new_prop->value) { | ||
109 | kfree(new_prop->name); | ||
110 | kfree(new_prop); | ||
111 | return -ENOMEM; | ||
112 | } | ||
113 | |||
114 | memcpy(new_prop->value, value, vd); | ||
115 | *prop = new_prop; | ||
116 | } | ||
117 | |||
118 | if (!more) { | ||
119 | old_prop = of_find_property(dn, new_prop->name, NULL); | ||
120 | if (old_prop) | ||
121 | prom_update_property(dn, new_prop, old_prop); | ||
122 | else | ||
123 | prom_add_property(dn, new_prop); | ||
124 | |||
125 | new_prop = NULL; | ||
126 | } | ||
127 | |||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int update_dt_node(u32 phandle) | ||
132 | { | ||
133 | struct update_props_workarea *upwa; | ||
134 | struct device_node *dn; | ||
135 | struct property *prop = NULL; | ||
136 | int i, rc; | ||
137 | char *prop_data; | ||
138 | char *rtas_buf; | ||
139 | int update_properties_token; | ||
140 | |||
141 | update_properties_token = rtas_token("ibm,update-properties"); | ||
142 | if (update_properties_token == RTAS_UNKNOWN_SERVICE) | ||
143 | return -EINVAL; | ||
144 | |||
145 | rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); | ||
146 | if (!rtas_buf) | ||
147 | return -ENOMEM; | ||
148 | |||
149 | dn = of_find_node_by_phandle(phandle); | ||
150 | if (!dn) { | ||
151 | kfree(rtas_buf); | ||
152 | return -ENOENT; | ||
153 | } | ||
154 | |||
155 | upwa = (struct update_props_workarea *)&rtas_buf[0]; | ||
156 | upwa->phandle = phandle; | ||
157 | |||
158 | do { | ||
159 | rc = mobility_rtas_call(update_properties_token, rtas_buf); | ||
160 | if (rc < 0) | ||
161 | break; | ||
162 | |||
163 | prop_data = rtas_buf + sizeof(*upwa); | ||
164 | |||
165 | for (i = 0; i < upwa->nprops; i++) { | ||
166 | char *prop_name; | ||
167 | u32 vd; | ||
168 | |||
169 | prop_name = prop_data + 1; | ||
170 | prop_data += strlen(prop_name) + 1; | ||
171 | vd = *prop_data++; | ||
172 | |||
173 | switch (vd) { | ||
174 | case 0x00000000: | ||
175 | /* name only property, nothing to do */ | ||
176 | break; | ||
177 | |||
178 | case 0x80000000: | ||
179 | prop = of_find_property(dn, prop_name, NULL); | ||
180 | prom_remove_property(dn, prop); | ||
181 | prop = NULL; | ||
182 | break; | ||
183 | |||
184 | default: | ||
185 | rc = update_dt_property(dn, &prop, prop_name, | ||
186 | vd, prop_data); | ||
187 | if (rc) { | ||
188 | printk(KERN_ERR "Could not update %s" | ||
189 | " property\n", prop_name); | ||
190 | } | ||
191 | |||
192 | prop_data += vd; | ||
193 | } | ||
194 | } | ||
195 | } while (rc == 1); | ||
196 | |||
197 | of_node_put(dn); | ||
198 | kfree(rtas_buf); | ||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static int add_dt_node(u32 parent_phandle, u32 drc_index) | ||
203 | { | ||
204 | struct device_node *dn; | ||
205 | struct device_node *parent_dn; | ||
206 | int rc; | ||
207 | |||
208 | dn = dlpar_configure_connector(drc_index); | ||
209 | if (!dn) | ||
210 | return -ENOENT; | ||
211 | |||
212 | parent_dn = of_find_node_by_phandle(parent_phandle); | ||
213 | if (!parent_dn) { | ||
214 | dlpar_free_cc_nodes(dn); | ||
215 | return -ENOENT; | ||
216 | } | ||
217 | |||
218 | dn->parent = parent_dn; | ||
219 | rc = dlpar_attach_node(dn); | ||
220 | if (rc) | ||
221 | dlpar_free_cc_nodes(dn); | ||
222 | |||
223 | of_node_put(parent_dn); | ||
224 | return rc; | ||
225 | } | ||
226 | |||
227 | static int pseries_devicetree_update(void) | ||
228 | { | ||
229 | char *rtas_buf; | ||
230 | u32 *data; | ||
231 | int update_nodes_token; | ||
232 | int rc; | ||
233 | |||
234 | update_nodes_token = rtas_token("ibm,update-nodes"); | ||
235 | if (update_nodes_token == RTAS_UNKNOWN_SERVICE) | ||
236 | return -EINVAL; | ||
237 | |||
238 | rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); | ||
239 | if (!rtas_buf) | ||
240 | return -ENOMEM; | ||
241 | |||
242 | do { | ||
243 | rc = mobility_rtas_call(update_nodes_token, rtas_buf); | ||
244 | if (rc && rc != 1) | ||
245 | break; | ||
246 | |||
247 | data = (u32 *)rtas_buf + 4; | ||
248 | while (*data & NODE_ACTION_MASK) { | ||
249 | int i; | ||
250 | u32 action = *data & NODE_ACTION_MASK; | ||
251 | int node_count = *data & NODE_COUNT_MASK; | ||
252 | |||
253 | data++; | ||
254 | |||
255 | for (i = 0; i < node_count; i++) { | ||
256 | u32 phandle = *data++; | ||
257 | u32 drc_index; | ||
258 | |||
259 | switch (action) { | ||
260 | case DELETE_DT_NODE: | ||
261 | delete_dt_node(phandle); | ||
262 | break; | ||
263 | case UPDATE_DT_NODE: | ||
264 | update_dt_node(phandle); | ||
265 | break; | ||
266 | case ADD_DT_NODE: | ||
267 | drc_index = *data++; | ||
268 | add_dt_node(phandle, drc_index); | ||
269 | break; | ||
270 | } | ||
271 | } | ||
272 | } | ||
273 | } while (rc == 1); | ||
274 | |||
275 | kfree(rtas_buf); | ||
276 | return rc; | ||
277 | } | ||
278 | |||
279 | void post_mobility_fixup(void) | ||
280 | { | ||
281 | int rc; | ||
282 | int activate_fw_token; | ||
283 | |||
284 | rc = pseries_devicetree_update(); | ||
285 | if (rc) { | ||
286 | printk(KERN_ERR "Initial post-mobility device tree update " | ||
287 | "failed: %d\n", rc); | ||
288 | return; | ||
289 | } | ||
290 | |||
291 | activate_fw_token = rtas_token("ibm,activate-firmware"); | ||
292 | if (activate_fw_token == RTAS_UNKNOWN_SERVICE) { | ||
293 | printk(KERN_ERR "Could not make post-mobility " | ||
294 | "activate-fw call.\n"); | ||
295 | return; | ||
296 | } | ||
297 | |||
298 | rc = rtas_call(activate_fw_token, 0, 1, NULL); | ||
299 | if (!rc) { | ||
300 | rc = pseries_devicetree_update(); | ||
301 | if (rc) | ||
302 | printk(KERN_ERR "Secondary post-mobility device tree " | ||
303 | "update failed: %d\n", rc); | ||
304 | } else { | ||
305 | printk(KERN_ERR "Post-mobility activate-fw failed: %d\n", rc); | ||
306 | return; | ||
307 | } | ||
308 | |||
309 | return; | ||
310 | } | ||
311 | |||
312 | static ssize_t migrate_store(struct class *class, struct class_attribute *attr, | ||
313 | const char *buf, size_t count) | ||
314 | { | ||
315 | struct rtas_args args; | ||
316 | u64 streamid; | ||
317 | int rc; | ||
318 | |||
319 | rc = strict_strtoull(buf, 0, &streamid); | ||
320 | if (rc) | ||
321 | return rc; | ||
322 | |||
323 | memset(&args, 0, sizeof(args)); | ||
324 | args.token = rtas_token("ibm,suspend-me"); | ||
325 | args.nargs = 2; | ||
326 | args.nret = 1; | ||
327 | |||
328 | args.args[0] = streamid >> 32 ; | ||
329 | args.args[1] = streamid & 0xffffffff; | ||
330 | args.rets = &args.args[args.nargs]; | ||
331 | |||
332 | do { | ||
333 | args.rets[0] = 0; | ||
334 | rc = rtas_ibm_suspend_me(&args); | ||
335 | if (!rc && args.rets[0] == RTAS_NOT_SUSPENDABLE) | ||
336 | ssleep(1); | ||
337 | } while (!rc && args.rets[0] == RTAS_NOT_SUSPENDABLE); | ||
338 | |||
339 | if (rc) | ||
340 | return rc; | ||
341 | else if (args.rets[0]) | ||
342 | return args.rets[0]; | ||
343 | |||
344 | post_mobility_fixup(); | ||
345 | return count; | ||
346 | } | ||
347 | |||
348 | static CLASS_ATTR(migration, S_IWUSR, NULL, migrate_store); | ||
349 | |||
350 | static int __init mobility_sysfs_init(void) | ||
351 | { | ||
352 | int rc; | ||
353 | |||
354 | mobility_kobj = kobject_create_and_add("mobility", kernel_kobj); | ||
355 | if (!mobility_kobj) | ||
356 | return -ENOMEM; | ||
357 | |||
358 | rc = sysfs_create_file(mobility_kobj, &class_attr_migration.attr); | ||
359 | |||
360 | return rc; | ||
361 | } | ||
362 | device_initcall(mobility_sysfs_init); | ||