]>
Commit | Line | Data |
---|---|---|
410bccf9 NF |
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> | |
b56eade5 | 15 | #include <linux/stat.h> |
410bccf9 NF |
16 | #include <linux/completion.h> |
17 | #include <linux/device.h> | |
18 | #include <linux/delay.h> | |
19 | #include <linux/slab.h> | |
5c35a02c | 20 | #include <linux/stringify.h> |
410bccf9 | 21 | |
8e83e905 | 22 | #include <asm/machdep.h> |
410bccf9 NF |
23 | #include <asm/rtas.h> |
24 | #include "pseries.h" | |
25 | ||
26 | static struct kobject *mobility_kobj; | |
27 | ||
28 | struct update_props_workarea { | |
f6ff0414 TD |
29 | __be32 phandle; |
30 | __be32 state; | |
31 | __be64 reserved; | |
32 | __be32 nprops; | |
d0ef4403 | 33 | } __packed; |
410bccf9 NF |
34 | |
35 | #define NODE_ACTION_MASK 0xff000000 | |
36 | #define NODE_COUNT_MASK 0x00ffffff | |
37 | ||
38 | #define DELETE_DT_NODE 0x01000000 | |
39 | #define UPDATE_DT_NODE 0x02000000 | |
40 | #define ADD_DT_NODE 0x03000000 | |
41 | ||
762ec157 | 42 | #define MIGRATION_SCOPE (1) |
675d8ee6 | 43 | #define PRRN_SCOPE -2 |
762ec157 NF |
44 | |
45 | static int mobility_rtas_call(int token, char *buf, s32 scope) | |
410bccf9 NF |
46 | { |
47 | int rc; | |
48 | ||
49 | spin_lock(&rtas_data_buf_lock); | |
50 | ||
51 | memcpy(rtas_data_buf, buf, RTAS_DATA_BUF_SIZE); | |
762ec157 | 52 | rc = rtas_call(token, 2, 1, NULL, rtas_data_buf, scope); |
410bccf9 NF |
53 | memcpy(buf, rtas_data_buf, RTAS_DATA_BUF_SIZE); |
54 | ||
55 | spin_unlock(&rtas_data_buf_lock); | |
56 | return rc; | |
57 | } | |
58 | ||
f6ff0414 | 59 | static int delete_dt_node(__be32 phandle) |
410bccf9 NF |
60 | { |
61 | struct device_node *dn; | |
62 | ||
f6ff0414 | 63 | dn = of_find_node_by_phandle(be32_to_cpu(phandle)); |
410bccf9 NF |
64 | if (!dn) |
65 | return -ENOENT; | |
66 | ||
67 | dlpar_detach_node(dn); | |
14cd820a | 68 | of_node_put(dn); |
410bccf9 NF |
69 | return 0; |
70 | } | |
71 | ||
72 | static int update_dt_property(struct device_node *dn, struct property **prop, | |
73 | const char *name, u32 vd, char *value) | |
74 | { | |
75 | struct property *new_prop = *prop; | |
410bccf9 NF |
76 | int more = 0; |
77 | ||
78 | /* A negative 'vd' value indicates that only part of the new property | |
79 | * value is contained in the buffer and we need to call | |
80 | * ibm,update-properties again to get the rest of the value. | |
81 | * | |
82 | * A negative value is also the two's compliment of the actual value. | |
83 | */ | |
84 | if (vd & 0x80000000) { | |
85 | vd = ~vd + 1; | |
86 | more = 1; | |
87 | } | |
88 | ||
89 | if (new_prop) { | |
90 | /* partial property fixup */ | |
91 | char *new_data = kzalloc(new_prop->length + vd, GFP_KERNEL); | |
92 | if (!new_data) | |
93 | return -ENOMEM; | |
94 | ||
95 | memcpy(new_data, new_prop->value, new_prop->length); | |
96 | memcpy(new_data + new_prop->length, value, vd); | |
97 | ||
98 | kfree(new_prop->value); | |
99 | new_prop->value = new_data; | |
100 | new_prop->length += vd; | |
101 | } else { | |
102 | new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); | |
103 | if (!new_prop) | |
104 | return -ENOMEM; | |
105 | ||
106 | new_prop->name = kstrdup(name, GFP_KERNEL); | |
107 | if (!new_prop->name) { | |
108 | kfree(new_prop); | |
109 | return -ENOMEM; | |
110 | } | |
111 | ||
112 | new_prop->length = vd; | |
113 | new_prop->value = kzalloc(new_prop->length, GFP_KERNEL); | |
114 | if (!new_prop->value) { | |
115 | kfree(new_prop->name); | |
116 | kfree(new_prop); | |
117 | return -ENOMEM; | |
118 | } | |
119 | ||
120 | memcpy(new_prop->value, value, vd); | |
121 | *prop = new_prop; | |
122 | } | |
123 | ||
124 | if (!more) { | |
79d1c712 | 125 | of_update_property(dn, new_prop); |
d8e533b4 | 126 | *prop = NULL; |
410bccf9 NF |
127 | } |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
f6ff0414 | 132 | static int update_dt_node(__be32 phandle, s32 scope) |
410bccf9 NF |
133 | { |
134 | struct update_props_workarea *upwa; | |
135 | struct device_node *dn; | |
136 | struct property *prop = NULL; | |
638a405f | 137 | int i, rc, rtas_rc; |
410bccf9 NF |
138 | char *prop_data; |
139 | char *rtas_buf; | |
140 | int update_properties_token; | |
f6ff0414 | 141 | u32 nprops; |
2e9b7b02 | 142 | u32 vd; |
410bccf9 NF |
143 | |
144 | update_properties_token = rtas_token("ibm,update-properties"); | |
145 | if (update_properties_token == RTAS_UNKNOWN_SERVICE) | |
146 | return -EINVAL; | |
147 | ||
148 | rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); | |
149 | if (!rtas_buf) | |
150 | return -ENOMEM; | |
151 | ||
f6ff0414 | 152 | dn = of_find_node_by_phandle(be32_to_cpu(phandle)); |
410bccf9 NF |
153 | if (!dn) { |
154 | kfree(rtas_buf); | |
155 | return -ENOENT; | |
156 | } | |
157 | ||
158 | upwa = (struct update_props_workarea *)&rtas_buf[0]; | |
159 | upwa->phandle = phandle; | |
160 | ||
161 | do { | |
638a405f | 162 | rtas_rc = mobility_rtas_call(update_properties_token, rtas_buf, |
762ec157 | 163 | scope); |
638a405f | 164 | if (rtas_rc < 0) |
410bccf9 NF |
165 | break; |
166 | ||
167 | prop_data = rtas_buf + sizeof(*upwa); | |
f6ff0414 | 168 | nprops = be32_to_cpu(upwa->nprops); |
410bccf9 | 169 | |
c8f5a57c TD |
170 | /* On the first call to ibm,update-properties for a node the |
171 | * the first property value descriptor contains an empty | |
172 | * property name, the property value length encoded as u32, | |
173 | * and the property value is the node path being updated. | |
2e9b7b02 | 174 | */ |
c8f5a57c TD |
175 | if (*prop_data == 0) { |
176 | prop_data++; | |
f6ff0414 | 177 | vd = be32_to_cpu(*(__be32 *)prop_data); |
c8f5a57c | 178 | prop_data += vd + sizeof(vd); |
f6ff0414 | 179 | nprops--; |
c8f5a57c | 180 | } |
2e9b7b02 | 181 | |
f6ff0414 | 182 | for (i = 0; i < nprops; i++) { |
410bccf9 | 183 | char *prop_name; |
410bccf9 | 184 | |
2e9b7b02 | 185 | prop_name = prop_data; |
410bccf9 | 186 | prop_data += strlen(prop_name) + 1; |
f6ff0414 | 187 | vd = be32_to_cpu(*(__be32 *)prop_data); |
2e9b7b02 | 188 | prop_data += sizeof(vd); |
410bccf9 NF |
189 | |
190 | switch (vd) { | |
191 | case 0x00000000: | |
192 | /* name only property, nothing to do */ | |
193 | break; | |
194 | ||
195 | case 0x80000000: | |
925e2d1d SJS |
196 | of_remove_property(dn, of_find_property(dn, |
197 | prop_name, NULL)); | |
410bccf9 NF |
198 | prop = NULL; |
199 | break; | |
200 | ||
201 | default: | |
202 | rc = update_dt_property(dn, &prop, prop_name, | |
203 | vd, prop_data); | |
204 | if (rc) { | |
205 | printk(KERN_ERR "Could not update %s" | |
206 | " property\n", prop_name); | |
207 | } | |
208 | ||
209 | prop_data += vd; | |
210 | } | |
211 | } | |
638a405f | 212 | } while (rtas_rc == 1); |
410bccf9 NF |
213 | |
214 | of_node_put(dn); | |
215 | kfree(rtas_buf); | |
216 | return 0; | |
217 | } | |
218 | ||
f6ff0414 | 219 | static int add_dt_node(__be32 parent_phandle, __be32 drc_index) |
410bccf9 NF |
220 | { |
221 | struct device_node *dn; | |
222 | struct device_node *parent_dn; | |
223 | int rc; | |
224 | ||
f6ff0414 | 225 | parent_dn = of_find_node_by_phandle(be32_to_cpu(parent_phandle)); |
8d5ff320 | 226 | if (!parent_dn) |
410bccf9 NF |
227 | return -ENOENT; |
228 | ||
8d5ff320 | 229 | dn = dlpar_configure_connector(drc_index, parent_dn); |
b537ca6f TD |
230 | if (!dn) { |
231 | of_node_put(parent_dn); | |
410bccf9 | 232 | return -ENOENT; |
b537ca6f | 233 | } |
410bccf9 | 234 | |
215ee763 | 235 | rc = dlpar_attach_node(dn, parent_dn); |
410bccf9 NF |
236 | if (rc) |
237 | dlpar_free_cc_nodes(dn); | |
238 | ||
239 | of_node_put(parent_dn); | |
240 | return rc; | |
241 | } | |
242 | ||
675d8ee6 JA |
243 | static void prrn_update_node(__be32 phandle) |
244 | { | |
fd12527a | 245 | struct pseries_hp_errorlog hp_elog; |
675d8ee6 JA |
246 | struct device_node *dn; |
247 | ||
248 | /* | |
249 | * If a node is found from a the given phandle, the phandle does not | |
250 | * represent the drc index of an LMB and we can ignore. | |
251 | */ | |
252 | dn = of_find_node_by_phandle(be32_to_cpu(phandle)); | |
253 | if (dn) { | |
254 | of_node_put(dn); | |
255 | return; | |
256 | } | |
257 | ||
fd12527a NF |
258 | hp_elog.resource = PSERIES_HP_ELOG_RESOURCE_MEM; |
259 | hp_elog.action = PSERIES_HP_ELOG_ACTION_READD; | |
260 | hp_elog.id_type = PSERIES_HP_ELOG_ID_DRC_INDEX; | |
261 | hp_elog._drc_u.drc_index = phandle; | |
675d8ee6 | 262 | |
fd12527a | 263 | handle_dlpar_errorlog(&hp_elog); |
675d8ee6 JA |
264 | } |
265 | ||
762ec157 | 266 | int pseries_devicetree_update(s32 scope) |
410bccf9 NF |
267 | { |
268 | char *rtas_buf; | |
f6ff0414 | 269 | __be32 *data; |
410bccf9 NF |
270 | int update_nodes_token; |
271 | int rc; | |
272 | ||
273 | update_nodes_token = rtas_token("ibm,update-nodes"); | |
274 | if (update_nodes_token == RTAS_UNKNOWN_SERVICE) | |
275 | return -EINVAL; | |
276 | ||
277 | rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); | |
278 | if (!rtas_buf) | |
279 | return -ENOMEM; | |
280 | ||
281 | do { | |
762ec157 | 282 | rc = mobility_rtas_call(update_nodes_token, rtas_buf, scope); |
410bccf9 NF |
283 | if (rc && rc != 1) |
284 | break; | |
285 | ||
f6ff0414 TD |
286 | data = (__be32 *)rtas_buf + 4; |
287 | while (be32_to_cpu(*data) & NODE_ACTION_MASK) { | |
410bccf9 | 288 | int i; |
f6ff0414 TD |
289 | u32 action = be32_to_cpu(*data) & NODE_ACTION_MASK; |
290 | u32 node_count = be32_to_cpu(*data) & NODE_COUNT_MASK; | |
410bccf9 NF |
291 | |
292 | data++; | |
293 | ||
294 | for (i = 0; i < node_count; i++) { | |
f6ff0414 TD |
295 | __be32 phandle = *data++; |
296 | __be32 drc_index; | |
410bccf9 NF |
297 | |
298 | switch (action) { | |
299 | case DELETE_DT_NODE: | |
300 | delete_dt_node(phandle); | |
301 | break; | |
302 | case UPDATE_DT_NODE: | |
762ec157 | 303 | update_dt_node(phandle, scope); |
675d8ee6 JA |
304 | |
305 | if (scope == PRRN_SCOPE) | |
306 | prrn_update_node(phandle); | |
307 | ||
410bccf9 NF |
308 | break; |
309 | case ADD_DT_NODE: | |
310 | drc_index = *data++; | |
311 | add_dt_node(phandle, drc_index); | |
312 | break; | |
313 | } | |
314 | } | |
315 | } | |
316 | } while (rc == 1); | |
317 | ||
318 | kfree(rtas_buf); | |
319 | return rc; | |
320 | } | |
321 | ||
322 | void post_mobility_fixup(void) | |
323 | { | |
324 | int rc; | |
325 | int activate_fw_token; | |
326 | ||
410bccf9 NF |
327 | activate_fw_token = rtas_token("ibm,activate-firmware"); |
328 | if (activate_fw_token == RTAS_UNKNOWN_SERVICE) { | |
329 | printk(KERN_ERR "Could not make post-mobility " | |
330 | "activate-fw call.\n"); | |
331 | return; | |
332 | } | |
333 | ||
39a33b59 HM |
334 | do { |
335 | rc = rtas_call(activate_fw_token, 0, 1, NULL); | |
336 | } while (rtas_busy_delay(rc)); | |
337 | ||
338 | if (rc) | |
410bccf9 | 339 | printk(KERN_ERR "Post-mobility activate-fw failed: %d\n", rc); |
39a33b59 HM |
340 | |
341 | rc = pseries_devicetree_update(MIGRATION_SCOPE); | |
342 | if (rc) | |
343 | printk(KERN_ERR "Post-mobility device tree update " | |
344 | "failed: %d\n", rc); | |
410bccf9 | 345 | |
921bc6cf ME |
346 | /* Possibly switch to a new RFI flush type */ |
347 | pseries_setup_rfi_flush(); | |
348 | ||
410bccf9 NF |
349 | return; |
350 | } | |
351 | ||
6f428096 GKH |
352 | static ssize_t migration_store(struct class *class, |
353 | struct class_attribute *attr, const char *buf, | |
354 | size_t count) | |
410bccf9 | 355 | { |
410bccf9 NF |
356 | u64 streamid; |
357 | int rc; | |
358 | ||
1618bd53 | 359 | rc = kstrtou64(buf, 0, &streamid); |
410bccf9 NF |
360 | if (rc) |
361 | return rc; | |
362 | ||
65b9fdad MB |
363 | stop_topology_update(); |
364 | ||
410bccf9 | 365 | do { |
c03e7374 TD |
366 | rc = rtas_ibm_suspend_me(streamid); |
367 | if (rc == -EAGAIN) | |
410bccf9 | 368 | ssleep(1); |
c03e7374 | 369 | } while (rc == -EAGAIN); |
410bccf9 NF |
370 | |
371 | if (rc) | |
372 | return rc; | |
410bccf9 NF |
373 | |
374 | post_mobility_fixup(); | |
65b9fdad MB |
375 | |
376 | start_topology_update(); | |
377 | ||
410bccf9 NF |
378 | return count; |
379 | } | |
380 | ||
288a298c TD |
381 | /* |
382 | * Used by drmgr to determine the kernel behavior of the migration interface. | |
383 | * | |
384 | * Version 1: Performs all PAPR requirements for migration including | |
385 | * firmware activation and device tree update. | |
386 | */ | |
387 | #define MIGRATION_API_VERSION 1 | |
388 | ||
6f428096 | 389 | static CLASS_ATTR_WO(migration); |
57ad583f | 390 | static CLASS_ATTR_STRING(api_version, 0444, __stringify(MIGRATION_API_VERSION)); |
410bccf9 NF |
391 | |
392 | static int __init mobility_sysfs_init(void) | |
393 | { | |
394 | int rc; | |
395 | ||
396 | mobility_kobj = kobject_create_and_add("mobility", kernel_kobj); | |
397 | if (!mobility_kobj) | |
398 | return -ENOMEM; | |
399 | ||
400 | rc = sysfs_create_file(mobility_kobj, &class_attr_migration.attr); | |
288a298c TD |
401 | if (rc) |
402 | pr_err("mobility: unable to create migration sysfs file (%d)\n", rc); | |
410bccf9 | 403 | |
288a298c TD |
404 | rc = sysfs_create_file(mobility_kobj, &class_attr_api_version.attr.attr); |
405 | if (rc) | |
406 | pr_err("mobility: unable to create api_version sysfs file (%d)\n", rc); | |
407 | ||
408 | return 0; | |
410bccf9 | 409 | } |
8e83e905 | 410 | machine_device_initcall(pseries, mobility_sysfs_init); |