aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/pseries/mobility.c
diff options
context:
space:
mode:
authorNathan Fontenot <nfont@austin.ibm.com>2010-09-10 05:42:36 -0400
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2010-10-13 01:19:03 -0400
commit410bccf978819394669dede571de878f4576fd3e (patch)
tree1037c9b036e79cbd1f5655ce078673ce62b7081f /arch/powerpc/platforms/pseries/mobility.c
parentd8862be1229534aac1768b8ac663e8fb2bb6ddf6 (diff)
powerpc/pseries: Partition migration in the kernel
Enable partition migration in the kernel. To do this a new sysfs file, /sys/kernel/mobility/migration, is created. In order to initiate a migration the stream id (generated by the HMC managing the system) is written to this file. After a migration occurs, and what is the majority of this code, the device tree needs to be updated for the new system the partition is running on. This is done via the ibm,update-nodes and ibm,update-properties rtas calls which return information regarding which nodes and properties of the device tree are to be added/removed/updated. Signed-off-by: Nathan Fontenot <nfont@austin.ibm.com> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms/pseries/mobility.c')
-rw-r--r--arch/powerpc/platforms/pseries/mobility.c362
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 00000000000..3e7f651e50a
--- /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
23static struct kobject *mobility_kobj;
24
25struct 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
39static 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
53static 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
65static 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
131static 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
202static 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
227static 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
279void 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
312static 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
348static CLASS_ATTR(migration, S_IWUSR, NULL, migrate_store);
349
350static 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}
362device_initcall(mobility_sysfs_init);