xref: /linux/arch/powerpc/platforms/pseries/mobility.c (revision 5c35a02c545a7bbe77f3a1ae337d9e29beed079b)
1410bccf9SNathan Fontenot /*
2410bccf9SNathan Fontenot  * Support for Partition Mobility/Migration
3410bccf9SNathan Fontenot  *
4410bccf9SNathan Fontenot  * Copyright (C) 2010 Nathan Fontenot
5410bccf9SNathan Fontenot  * Copyright (C) 2010 IBM Corporation
6410bccf9SNathan Fontenot  *
7410bccf9SNathan Fontenot  * This program is free software; you can redistribute it and/or
8410bccf9SNathan Fontenot  * modify it under the terms of the GNU General Public License version
9410bccf9SNathan Fontenot  * 2 as published by the Free Software Foundation.
10410bccf9SNathan Fontenot  */
11410bccf9SNathan Fontenot 
12410bccf9SNathan Fontenot #include <linux/kernel.h>
13410bccf9SNathan Fontenot #include <linux/kobject.h>
14410bccf9SNathan Fontenot #include <linux/smp.h>
15b56eade5SPaul Gortmaker #include <linux/stat.h>
16410bccf9SNathan Fontenot #include <linux/completion.h>
17410bccf9SNathan Fontenot #include <linux/device.h>
18410bccf9SNathan Fontenot #include <linux/delay.h>
19410bccf9SNathan Fontenot #include <linux/slab.h>
20*5c35a02cSChristophe Leroy #include <linux/stringify.h>
21410bccf9SNathan Fontenot 
228e83e905SMichael Ellerman #include <asm/machdep.h>
23410bccf9SNathan Fontenot #include <asm/rtas.h>
24410bccf9SNathan Fontenot #include "pseries.h"
25410bccf9SNathan Fontenot 
26410bccf9SNathan Fontenot static struct kobject *mobility_kobj;
27410bccf9SNathan Fontenot 
28410bccf9SNathan Fontenot struct update_props_workarea {
29f6ff0414STyrel Datwyler 	__be32 phandle;
30f6ff0414STyrel Datwyler 	__be32 state;
31f6ff0414STyrel Datwyler 	__be64 reserved;
32f6ff0414STyrel Datwyler 	__be32 nprops;
33d0ef4403STyrel Datwyler } __packed;
34410bccf9SNathan Fontenot 
35410bccf9SNathan Fontenot #define NODE_ACTION_MASK	0xff000000
36410bccf9SNathan Fontenot #define NODE_COUNT_MASK		0x00ffffff
37410bccf9SNathan Fontenot 
38410bccf9SNathan Fontenot #define DELETE_DT_NODE	0x01000000
39410bccf9SNathan Fontenot #define UPDATE_DT_NODE	0x02000000
40410bccf9SNathan Fontenot #define ADD_DT_NODE	0x03000000
41410bccf9SNathan Fontenot 
42762ec157SNathan Fontenot #define MIGRATION_SCOPE	(1)
43675d8ee6SJohn Allen #define PRRN_SCOPE -2
44762ec157SNathan Fontenot 
45762ec157SNathan Fontenot static int mobility_rtas_call(int token, char *buf, s32 scope)
46410bccf9SNathan Fontenot {
47410bccf9SNathan Fontenot 	int rc;
48410bccf9SNathan Fontenot 
49410bccf9SNathan Fontenot 	spin_lock(&rtas_data_buf_lock);
50410bccf9SNathan Fontenot 
51410bccf9SNathan Fontenot 	memcpy(rtas_data_buf, buf, RTAS_DATA_BUF_SIZE);
52762ec157SNathan Fontenot 	rc = rtas_call(token, 2, 1, NULL, rtas_data_buf, scope);
53410bccf9SNathan Fontenot 	memcpy(buf, rtas_data_buf, RTAS_DATA_BUF_SIZE);
54410bccf9SNathan Fontenot 
55410bccf9SNathan Fontenot 	spin_unlock(&rtas_data_buf_lock);
56410bccf9SNathan Fontenot 	return rc;
57410bccf9SNathan Fontenot }
58410bccf9SNathan Fontenot 
59f6ff0414STyrel Datwyler static int delete_dt_node(__be32 phandle)
60410bccf9SNathan Fontenot {
61410bccf9SNathan Fontenot 	struct device_node *dn;
62410bccf9SNathan Fontenot 
63f6ff0414STyrel Datwyler 	dn = of_find_node_by_phandle(be32_to_cpu(phandle));
64410bccf9SNathan Fontenot 	if (!dn)
65410bccf9SNathan Fontenot 		return -ENOENT;
66410bccf9SNathan Fontenot 
67410bccf9SNathan Fontenot 	dlpar_detach_node(dn);
6814cd820aSTyrel Datwyler 	of_node_put(dn);
69410bccf9SNathan Fontenot 	return 0;
70410bccf9SNathan Fontenot }
71410bccf9SNathan Fontenot 
72410bccf9SNathan Fontenot static int update_dt_property(struct device_node *dn, struct property **prop,
73410bccf9SNathan Fontenot 			      const char *name, u32 vd, char *value)
74410bccf9SNathan Fontenot {
75410bccf9SNathan Fontenot 	struct property *new_prop = *prop;
76410bccf9SNathan Fontenot 	int more = 0;
77410bccf9SNathan Fontenot 
78410bccf9SNathan Fontenot 	/* A negative 'vd' value indicates that only part of the new property
79410bccf9SNathan Fontenot 	 * value is contained in the buffer and we need to call
80410bccf9SNathan Fontenot 	 * ibm,update-properties again to get the rest of the value.
81410bccf9SNathan Fontenot 	 *
82410bccf9SNathan Fontenot 	 * A negative value is also the two's compliment of the actual value.
83410bccf9SNathan Fontenot 	 */
84410bccf9SNathan Fontenot 	if (vd & 0x80000000) {
85410bccf9SNathan Fontenot 		vd = ~vd + 1;
86410bccf9SNathan Fontenot 		more = 1;
87410bccf9SNathan Fontenot 	}
88410bccf9SNathan Fontenot 
89410bccf9SNathan Fontenot 	if (new_prop) {
90410bccf9SNathan Fontenot 		/* partial property fixup */
91410bccf9SNathan Fontenot 		char *new_data = kzalloc(new_prop->length + vd, GFP_KERNEL);
92410bccf9SNathan Fontenot 		if (!new_data)
93410bccf9SNathan Fontenot 			return -ENOMEM;
94410bccf9SNathan Fontenot 
95410bccf9SNathan Fontenot 		memcpy(new_data, new_prop->value, new_prop->length);
96410bccf9SNathan Fontenot 		memcpy(new_data + new_prop->length, value, vd);
97410bccf9SNathan Fontenot 
98410bccf9SNathan Fontenot 		kfree(new_prop->value);
99410bccf9SNathan Fontenot 		new_prop->value = new_data;
100410bccf9SNathan Fontenot 		new_prop->length += vd;
101410bccf9SNathan Fontenot 	} else {
102410bccf9SNathan Fontenot 		new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);
103410bccf9SNathan Fontenot 		if (!new_prop)
104410bccf9SNathan Fontenot 			return -ENOMEM;
105410bccf9SNathan Fontenot 
106410bccf9SNathan Fontenot 		new_prop->name = kstrdup(name, GFP_KERNEL);
107410bccf9SNathan Fontenot 		if (!new_prop->name) {
108410bccf9SNathan Fontenot 			kfree(new_prop);
109410bccf9SNathan Fontenot 			return -ENOMEM;
110410bccf9SNathan Fontenot 		}
111410bccf9SNathan Fontenot 
112410bccf9SNathan Fontenot 		new_prop->length = vd;
113410bccf9SNathan Fontenot 		new_prop->value = kzalloc(new_prop->length, GFP_KERNEL);
114410bccf9SNathan Fontenot 		if (!new_prop->value) {
115410bccf9SNathan Fontenot 			kfree(new_prop->name);
116410bccf9SNathan Fontenot 			kfree(new_prop);
117410bccf9SNathan Fontenot 			return -ENOMEM;
118410bccf9SNathan Fontenot 		}
119410bccf9SNathan Fontenot 
120410bccf9SNathan Fontenot 		memcpy(new_prop->value, value, vd);
121410bccf9SNathan Fontenot 		*prop = new_prop;
122410bccf9SNathan Fontenot 	}
123410bccf9SNathan Fontenot 
124410bccf9SNathan Fontenot 	if (!more) {
12579d1c712SNathan Fontenot 		of_update_property(dn, new_prop);
126d8e533b4STyrel Datwyler 		*prop = NULL;
127410bccf9SNathan Fontenot 	}
128410bccf9SNathan Fontenot 
129410bccf9SNathan Fontenot 	return 0;
130410bccf9SNathan Fontenot }
131410bccf9SNathan Fontenot 
132f6ff0414STyrel Datwyler static int update_dt_node(__be32 phandle, s32 scope)
133410bccf9SNathan Fontenot {
134410bccf9SNathan Fontenot 	struct update_props_workarea *upwa;
135410bccf9SNathan Fontenot 	struct device_node *dn;
136410bccf9SNathan Fontenot 	struct property *prop = NULL;
137638a405fSTyrel Datwyler 	int i, rc, rtas_rc;
138410bccf9SNathan Fontenot 	char *prop_data;
139410bccf9SNathan Fontenot 	char *rtas_buf;
140410bccf9SNathan Fontenot 	int update_properties_token;
141f6ff0414STyrel Datwyler 	u32 nprops;
1422e9b7b02SNathan Fontenot 	u32 vd;
143410bccf9SNathan Fontenot 
144410bccf9SNathan Fontenot 	update_properties_token = rtas_token("ibm,update-properties");
145410bccf9SNathan Fontenot 	if (update_properties_token == RTAS_UNKNOWN_SERVICE)
146410bccf9SNathan Fontenot 		return -EINVAL;
147410bccf9SNathan Fontenot 
148410bccf9SNathan Fontenot 	rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL);
149410bccf9SNathan Fontenot 	if (!rtas_buf)
150410bccf9SNathan Fontenot 		return -ENOMEM;
151410bccf9SNathan Fontenot 
152f6ff0414STyrel Datwyler 	dn = of_find_node_by_phandle(be32_to_cpu(phandle));
153410bccf9SNathan Fontenot 	if (!dn) {
154410bccf9SNathan Fontenot 		kfree(rtas_buf);
155410bccf9SNathan Fontenot 		return -ENOENT;
156410bccf9SNathan Fontenot 	}
157410bccf9SNathan Fontenot 
158410bccf9SNathan Fontenot 	upwa = (struct update_props_workarea *)&rtas_buf[0];
159410bccf9SNathan Fontenot 	upwa->phandle = phandle;
160410bccf9SNathan Fontenot 
161410bccf9SNathan Fontenot 	do {
162638a405fSTyrel Datwyler 		rtas_rc = mobility_rtas_call(update_properties_token, rtas_buf,
163762ec157SNathan Fontenot 					scope);
164638a405fSTyrel Datwyler 		if (rtas_rc < 0)
165410bccf9SNathan Fontenot 			break;
166410bccf9SNathan Fontenot 
167410bccf9SNathan Fontenot 		prop_data = rtas_buf + sizeof(*upwa);
168f6ff0414STyrel Datwyler 		nprops = be32_to_cpu(upwa->nprops);
169410bccf9SNathan Fontenot 
170c8f5a57cSTyrel Datwyler 		/* On the first call to ibm,update-properties for a node the
171c8f5a57cSTyrel Datwyler 		 * the first property value descriptor contains an empty
172c8f5a57cSTyrel Datwyler 		 * property name, the property value length encoded as u32,
173c8f5a57cSTyrel Datwyler 		 * and the property value is the node path being updated.
1742e9b7b02SNathan Fontenot 		 */
175c8f5a57cSTyrel Datwyler 		if (*prop_data == 0) {
176c8f5a57cSTyrel Datwyler 			prop_data++;
177f6ff0414STyrel Datwyler 			vd = be32_to_cpu(*(__be32 *)prop_data);
178c8f5a57cSTyrel Datwyler 			prop_data += vd + sizeof(vd);
179f6ff0414STyrel Datwyler 			nprops--;
180c8f5a57cSTyrel Datwyler 		}
1812e9b7b02SNathan Fontenot 
182f6ff0414STyrel Datwyler 		for (i = 0; i < nprops; i++) {
1832e9b7b02SNathan Fontenot 			char *prop_name;
1842e9b7b02SNathan Fontenot 
1852e9b7b02SNathan Fontenot 			prop_name = prop_data;
1862e9b7b02SNathan Fontenot 			prop_data += strlen(prop_name) + 1;
187f6ff0414STyrel Datwyler 			vd = be32_to_cpu(*(__be32 *)prop_data);
1882e9b7b02SNathan Fontenot 			prop_data += sizeof(vd);
189410bccf9SNathan Fontenot 
190410bccf9SNathan Fontenot 			switch (vd) {
191410bccf9SNathan Fontenot 			case 0x00000000:
192410bccf9SNathan Fontenot 				/* name only property, nothing to do */
193410bccf9SNathan Fontenot 				break;
194410bccf9SNathan Fontenot 
195410bccf9SNathan Fontenot 			case 0x80000000:
196925e2d1dSSuraj Jitindar Singh 				of_remove_property(dn, of_find_property(dn,
197925e2d1dSSuraj Jitindar Singh 							prop_name, NULL));
198410bccf9SNathan Fontenot 				prop = NULL;
199410bccf9SNathan Fontenot 				break;
200410bccf9SNathan Fontenot 
201410bccf9SNathan Fontenot 			default:
202410bccf9SNathan Fontenot 				rc = update_dt_property(dn, &prop, prop_name,
203410bccf9SNathan Fontenot 							vd, prop_data);
204410bccf9SNathan Fontenot 				if (rc) {
205410bccf9SNathan Fontenot 					printk(KERN_ERR "Could not update %s"
206410bccf9SNathan Fontenot 					       " property\n", prop_name);
207410bccf9SNathan Fontenot 				}
208410bccf9SNathan Fontenot 
209410bccf9SNathan Fontenot 				prop_data += vd;
210410bccf9SNathan Fontenot 			}
211410bccf9SNathan Fontenot 		}
212638a405fSTyrel Datwyler 	} while (rtas_rc == 1);
213410bccf9SNathan Fontenot 
214410bccf9SNathan Fontenot 	of_node_put(dn);
215410bccf9SNathan Fontenot 	kfree(rtas_buf);
216410bccf9SNathan Fontenot 	return 0;
217410bccf9SNathan Fontenot }
218410bccf9SNathan Fontenot 
219f6ff0414STyrel Datwyler static int add_dt_node(__be32 parent_phandle, __be32 drc_index)
220410bccf9SNathan Fontenot {
221410bccf9SNathan Fontenot 	struct device_node *dn;
222410bccf9SNathan Fontenot 	struct device_node *parent_dn;
223410bccf9SNathan Fontenot 	int rc;
224410bccf9SNathan Fontenot 
225f6ff0414STyrel Datwyler 	parent_dn = of_find_node_by_phandle(be32_to_cpu(parent_phandle));
2268d5ff320STyrel Datwyler 	if (!parent_dn)
2278d5ff320STyrel Datwyler 		return -ENOENT;
2288d5ff320STyrel Datwyler 
2298d5ff320STyrel Datwyler 	dn = dlpar_configure_connector(drc_index, parent_dn);
230b537ca6fSTyrel Datwyler 	if (!dn) {
231b537ca6fSTyrel Datwyler 		of_node_put(parent_dn);
232410bccf9SNathan Fontenot 		return -ENOENT;
233b537ca6fSTyrel Datwyler 	}
234410bccf9SNathan Fontenot 
235215ee763SRob Herring 	rc = dlpar_attach_node(dn, parent_dn);
236410bccf9SNathan Fontenot 	if (rc)
237410bccf9SNathan Fontenot 		dlpar_free_cc_nodes(dn);
238410bccf9SNathan Fontenot 
239410bccf9SNathan Fontenot 	of_node_put(parent_dn);
240410bccf9SNathan Fontenot 	return rc;
241410bccf9SNathan Fontenot }
242410bccf9SNathan Fontenot 
243675d8ee6SJohn Allen static void prrn_update_node(__be32 phandle)
244675d8ee6SJohn Allen {
245675d8ee6SJohn Allen 	struct pseries_hp_errorlog *hp_elog;
246675d8ee6SJohn Allen 	struct device_node *dn;
247675d8ee6SJohn Allen 
248675d8ee6SJohn Allen 	/*
249675d8ee6SJohn Allen 	 * If a node is found from a the given phandle, the phandle does not
250675d8ee6SJohn Allen 	 * represent the drc index of an LMB and we can ignore.
251675d8ee6SJohn Allen 	 */
252675d8ee6SJohn Allen 	dn = of_find_node_by_phandle(be32_to_cpu(phandle));
253675d8ee6SJohn Allen 	if (dn) {
254675d8ee6SJohn Allen 		of_node_put(dn);
255675d8ee6SJohn Allen 		return;
256675d8ee6SJohn Allen 	}
257675d8ee6SJohn Allen 
258675d8ee6SJohn Allen 	hp_elog = kzalloc(sizeof(*hp_elog), GFP_KERNEL);
259675d8ee6SJohn Allen 	if(!hp_elog)
260675d8ee6SJohn Allen 		return;
261675d8ee6SJohn Allen 
262675d8ee6SJohn Allen 	hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_MEM;
263675d8ee6SJohn Allen 	hp_elog->action = PSERIES_HP_ELOG_ACTION_READD;
264675d8ee6SJohn Allen 	hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_INDEX;
265675d8ee6SJohn Allen 	hp_elog->_drc_u.drc_index = phandle;
266675d8ee6SJohn Allen 
267675d8ee6SJohn Allen 	queue_hotplug_event(hp_elog, NULL, NULL);
268675d8ee6SJohn Allen 
269675d8ee6SJohn Allen 	kfree(hp_elog);
270675d8ee6SJohn Allen }
271675d8ee6SJohn Allen 
272762ec157SNathan Fontenot int pseries_devicetree_update(s32 scope)
273410bccf9SNathan Fontenot {
274410bccf9SNathan Fontenot 	char *rtas_buf;
275f6ff0414STyrel Datwyler 	__be32 *data;
276410bccf9SNathan Fontenot 	int update_nodes_token;
277410bccf9SNathan Fontenot 	int rc;
278410bccf9SNathan Fontenot 
279410bccf9SNathan Fontenot 	update_nodes_token = rtas_token("ibm,update-nodes");
280410bccf9SNathan Fontenot 	if (update_nodes_token == RTAS_UNKNOWN_SERVICE)
281410bccf9SNathan Fontenot 		return -EINVAL;
282410bccf9SNathan Fontenot 
283410bccf9SNathan Fontenot 	rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL);
284410bccf9SNathan Fontenot 	if (!rtas_buf)
285410bccf9SNathan Fontenot 		return -ENOMEM;
286410bccf9SNathan Fontenot 
287410bccf9SNathan Fontenot 	do {
288762ec157SNathan Fontenot 		rc = mobility_rtas_call(update_nodes_token, rtas_buf, scope);
289410bccf9SNathan Fontenot 		if (rc && rc != 1)
290410bccf9SNathan Fontenot 			break;
291410bccf9SNathan Fontenot 
292f6ff0414STyrel Datwyler 		data = (__be32 *)rtas_buf + 4;
293f6ff0414STyrel Datwyler 		while (be32_to_cpu(*data) & NODE_ACTION_MASK) {
294410bccf9SNathan Fontenot 			int i;
295f6ff0414STyrel Datwyler 			u32 action = be32_to_cpu(*data) & NODE_ACTION_MASK;
296f6ff0414STyrel Datwyler 			u32 node_count = be32_to_cpu(*data) & NODE_COUNT_MASK;
297410bccf9SNathan Fontenot 
298410bccf9SNathan Fontenot 			data++;
299410bccf9SNathan Fontenot 
300410bccf9SNathan Fontenot 			for (i = 0; i < node_count; i++) {
301f6ff0414STyrel Datwyler 				__be32 phandle = *data++;
302f6ff0414STyrel Datwyler 				__be32 drc_index;
303410bccf9SNathan Fontenot 
304410bccf9SNathan Fontenot 				switch (action) {
305410bccf9SNathan Fontenot 				case DELETE_DT_NODE:
306410bccf9SNathan Fontenot 					delete_dt_node(phandle);
307410bccf9SNathan Fontenot 					break;
308410bccf9SNathan Fontenot 				case UPDATE_DT_NODE:
309762ec157SNathan Fontenot 					update_dt_node(phandle, scope);
310675d8ee6SJohn Allen 
311675d8ee6SJohn Allen 					if (scope == PRRN_SCOPE)
312675d8ee6SJohn Allen 						prrn_update_node(phandle);
313675d8ee6SJohn Allen 
314410bccf9SNathan Fontenot 					break;
315410bccf9SNathan Fontenot 				case ADD_DT_NODE:
316410bccf9SNathan Fontenot 					drc_index = *data++;
317410bccf9SNathan Fontenot 					add_dt_node(phandle, drc_index);
318410bccf9SNathan Fontenot 					break;
319410bccf9SNathan Fontenot 				}
320410bccf9SNathan Fontenot 			}
321410bccf9SNathan Fontenot 		}
322410bccf9SNathan Fontenot 	} while (rc == 1);
323410bccf9SNathan Fontenot 
324410bccf9SNathan Fontenot 	kfree(rtas_buf);
325410bccf9SNathan Fontenot 	return rc;
326410bccf9SNathan Fontenot }
327410bccf9SNathan Fontenot 
328410bccf9SNathan Fontenot void post_mobility_fixup(void)
329410bccf9SNathan Fontenot {
330410bccf9SNathan Fontenot 	int rc;
331410bccf9SNathan Fontenot 	int activate_fw_token;
332410bccf9SNathan Fontenot 
333410bccf9SNathan Fontenot 	activate_fw_token = rtas_token("ibm,activate-firmware");
334410bccf9SNathan Fontenot 	if (activate_fw_token == RTAS_UNKNOWN_SERVICE) {
335410bccf9SNathan Fontenot 		printk(KERN_ERR "Could not make post-mobility "
336410bccf9SNathan Fontenot 		       "activate-fw call.\n");
337410bccf9SNathan Fontenot 		return;
338410bccf9SNathan Fontenot 	}
339410bccf9SNathan Fontenot 
34039a33b59SHaren Myneni 	do {
341410bccf9SNathan Fontenot 		rc = rtas_call(activate_fw_token, 0, 1, NULL);
34239a33b59SHaren Myneni 	} while (rtas_busy_delay(rc));
34339a33b59SHaren Myneni 
34439a33b59SHaren Myneni 	if (rc)
34539a33b59SHaren Myneni 		printk(KERN_ERR "Post-mobility activate-fw failed: %d\n", rc);
34639a33b59SHaren Myneni 
347762ec157SNathan Fontenot 	rc = pseries_devicetree_update(MIGRATION_SCOPE);
348410bccf9SNathan Fontenot 	if (rc)
34939a33b59SHaren Myneni 		printk(KERN_ERR "Post-mobility device tree update "
35039a33b59SHaren Myneni 			"failed: %d\n", rc);
351410bccf9SNathan Fontenot 
352921bc6cfSMichael Ellerman 	/* Possibly switch to a new RFI flush type */
353921bc6cfSMichael Ellerman 	pseries_setup_rfi_flush();
354921bc6cfSMichael Ellerman 
355410bccf9SNathan Fontenot 	return;
356410bccf9SNathan Fontenot }
357410bccf9SNathan Fontenot 
3586f428096SGreg Kroah-Hartman static ssize_t migration_store(struct class *class,
3596f428096SGreg Kroah-Hartman 			       struct class_attribute *attr, const char *buf,
3606f428096SGreg Kroah-Hartman 			       size_t count)
361410bccf9SNathan Fontenot {
362410bccf9SNathan Fontenot 	u64 streamid;
363410bccf9SNathan Fontenot 	int rc;
364410bccf9SNathan Fontenot 
3651618bd53SDaniel Walter 	rc = kstrtou64(buf, 0, &streamid);
366410bccf9SNathan Fontenot 	if (rc)
367410bccf9SNathan Fontenot 		return rc;
368410bccf9SNathan Fontenot 
369410bccf9SNathan Fontenot 	do {
370c03e7374STyrel Datwyler 		rc = rtas_ibm_suspend_me(streamid);
371c03e7374STyrel Datwyler 		if (rc == -EAGAIN)
372410bccf9SNathan Fontenot 			ssleep(1);
373c03e7374STyrel Datwyler 	} while (rc == -EAGAIN);
374410bccf9SNathan Fontenot 
375410bccf9SNathan Fontenot 	if (rc)
376410bccf9SNathan Fontenot 		return rc;
377410bccf9SNathan Fontenot 
378410bccf9SNathan Fontenot 	post_mobility_fixup();
379410bccf9SNathan Fontenot 	return count;
380410bccf9SNathan Fontenot }
381410bccf9SNathan Fontenot 
382288a298cSTyrel Datwyler /*
383288a298cSTyrel Datwyler  * Used by drmgr to determine the kernel behavior of the migration interface.
384288a298cSTyrel Datwyler  *
385288a298cSTyrel Datwyler  * Version 1: Performs all PAPR requirements for migration including
386288a298cSTyrel Datwyler  *	firmware activation and device tree update.
387288a298cSTyrel Datwyler  */
388288a298cSTyrel Datwyler #define MIGRATION_API_VERSION	1
389288a298cSTyrel Datwyler 
3906f428096SGreg Kroah-Hartman static CLASS_ATTR_WO(migration);
39157ad583fSRussell Currey static CLASS_ATTR_STRING(api_version, 0444, __stringify(MIGRATION_API_VERSION));
392410bccf9SNathan Fontenot 
393410bccf9SNathan Fontenot static int __init mobility_sysfs_init(void)
394410bccf9SNathan Fontenot {
395410bccf9SNathan Fontenot 	int rc;
396410bccf9SNathan Fontenot 
397410bccf9SNathan Fontenot 	mobility_kobj = kobject_create_and_add("mobility", kernel_kobj);
398410bccf9SNathan Fontenot 	if (!mobility_kobj)
399410bccf9SNathan Fontenot 		return -ENOMEM;
400410bccf9SNathan Fontenot 
401410bccf9SNathan Fontenot 	rc = sysfs_create_file(mobility_kobj, &class_attr_migration.attr);
402288a298cSTyrel Datwyler 	if (rc)
403288a298cSTyrel Datwyler 		pr_err("mobility: unable to create migration sysfs file (%d)\n", rc);
404410bccf9SNathan Fontenot 
405288a298cSTyrel Datwyler 	rc = sysfs_create_file(mobility_kobj, &class_attr_api_version.attr.attr);
406288a298cSTyrel Datwyler 	if (rc)
407288a298cSTyrel Datwyler 		pr_err("mobility: unable to create api_version sysfs file (%d)\n", rc);
408288a298cSTyrel Datwyler 
409288a298cSTyrel Datwyler 	return 0;
410410bccf9SNathan Fontenot }
4118e83e905SMichael Ellerman machine_device_initcall(pseries, mobility_sysfs_init);
412