xref: /linux/arch/powerpc/platforms/pseries/mobility.c (revision 638a405fb52cfb2e7f559e869dd0291420b9e84d)
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>
20410bccf9SNathan Fontenot 
21410bccf9SNathan Fontenot #include <asm/rtas.h>
22410bccf9SNathan Fontenot #include "pseries.h"
23410bccf9SNathan Fontenot 
24410bccf9SNathan Fontenot static struct kobject *mobility_kobj;
25410bccf9SNathan Fontenot 
26410bccf9SNathan Fontenot struct update_props_workarea {
27410bccf9SNathan Fontenot 	u32 phandle;
28410bccf9SNathan Fontenot 	u32 state;
29410bccf9SNathan Fontenot 	u64 reserved;
30410bccf9SNathan Fontenot 	u32 nprops;
31410bccf9SNathan Fontenot };
32410bccf9SNathan Fontenot 
33410bccf9SNathan Fontenot #define NODE_ACTION_MASK	0xff000000
34410bccf9SNathan Fontenot #define NODE_COUNT_MASK		0x00ffffff
35410bccf9SNathan Fontenot 
36410bccf9SNathan Fontenot #define DELETE_DT_NODE	0x01000000
37410bccf9SNathan Fontenot #define UPDATE_DT_NODE	0x02000000
38410bccf9SNathan Fontenot #define ADD_DT_NODE	0x03000000
39410bccf9SNathan Fontenot 
40762ec157SNathan Fontenot #define MIGRATION_SCOPE	(1)
41762ec157SNathan Fontenot 
42762ec157SNathan Fontenot static int mobility_rtas_call(int token, char *buf, s32 scope)
43410bccf9SNathan Fontenot {
44410bccf9SNathan Fontenot 	int rc;
45410bccf9SNathan Fontenot 
46410bccf9SNathan Fontenot 	spin_lock(&rtas_data_buf_lock);
47410bccf9SNathan Fontenot 
48410bccf9SNathan Fontenot 	memcpy(rtas_data_buf, buf, RTAS_DATA_BUF_SIZE);
49762ec157SNathan Fontenot 	rc = rtas_call(token, 2, 1, NULL, rtas_data_buf, scope);
50410bccf9SNathan Fontenot 	memcpy(buf, rtas_data_buf, RTAS_DATA_BUF_SIZE);
51410bccf9SNathan Fontenot 
52410bccf9SNathan Fontenot 	spin_unlock(&rtas_data_buf_lock);
53410bccf9SNathan Fontenot 	return rc;
54410bccf9SNathan Fontenot }
55410bccf9SNathan Fontenot 
56410bccf9SNathan Fontenot static int delete_dt_node(u32 phandle)
57410bccf9SNathan Fontenot {
58410bccf9SNathan Fontenot 	struct device_node *dn;
59410bccf9SNathan Fontenot 
60410bccf9SNathan Fontenot 	dn = of_find_node_by_phandle(phandle);
61410bccf9SNathan Fontenot 	if (!dn)
62410bccf9SNathan Fontenot 		return -ENOENT;
63410bccf9SNathan Fontenot 
64410bccf9SNathan Fontenot 	dlpar_detach_node(dn);
65410bccf9SNathan Fontenot 	return 0;
66410bccf9SNathan Fontenot }
67410bccf9SNathan Fontenot 
68410bccf9SNathan Fontenot static int update_dt_property(struct device_node *dn, struct property **prop,
69410bccf9SNathan Fontenot 			      const char *name, u32 vd, char *value)
70410bccf9SNathan Fontenot {
71410bccf9SNathan Fontenot 	struct property *new_prop = *prop;
72410bccf9SNathan Fontenot 	int more = 0;
73410bccf9SNathan Fontenot 
74410bccf9SNathan Fontenot 	/* A negative 'vd' value indicates that only part of the new property
75410bccf9SNathan Fontenot 	 * value is contained in the buffer and we need to call
76410bccf9SNathan Fontenot 	 * ibm,update-properties again to get the rest of the value.
77410bccf9SNathan Fontenot 	 *
78410bccf9SNathan Fontenot 	 * A negative value is also the two's compliment of the actual value.
79410bccf9SNathan Fontenot 	 */
80410bccf9SNathan Fontenot 	if (vd & 0x80000000) {
81410bccf9SNathan Fontenot 		vd = ~vd + 1;
82410bccf9SNathan Fontenot 		more = 1;
83410bccf9SNathan Fontenot 	}
84410bccf9SNathan Fontenot 
85410bccf9SNathan Fontenot 	if (new_prop) {
86410bccf9SNathan Fontenot 		/* partial property fixup */
87410bccf9SNathan Fontenot 		char *new_data = kzalloc(new_prop->length + vd, GFP_KERNEL);
88410bccf9SNathan Fontenot 		if (!new_data)
89410bccf9SNathan Fontenot 			return -ENOMEM;
90410bccf9SNathan Fontenot 
91410bccf9SNathan Fontenot 		memcpy(new_data, new_prop->value, new_prop->length);
92410bccf9SNathan Fontenot 		memcpy(new_data + new_prop->length, value, vd);
93410bccf9SNathan Fontenot 
94410bccf9SNathan Fontenot 		kfree(new_prop->value);
95410bccf9SNathan Fontenot 		new_prop->value = new_data;
96410bccf9SNathan Fontenot 		new_prop->length += vd;
97410bccf9SNathan Fontenot 	} else {
98410bccf9SNathan Fontenot 		new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);
99410bccf9SNathan Fontenot 		if (!new_prop)
100410bccf9SNathan Fontenot 			return -ENOMEM;
101410bccf9SNathan Fontenot 
102410bccf9SNathan Fontenot 		new_prop->name = kstrdup(name, GFP_KERNEL);
103410bccf9SNathan Fontenot 		if (!new_prop->name) {
104410bccf9SNathan Fontenot 			kfree(new_prop);
105410bccf9SNathan Fontenot 			return -ENOMEM;
106410bccf9SNathan Fontenot 		}
107410bccf9SNathan Fontenot 
108410bccf9SNathan Fontenot 		new_prop->length = vd;
109410bccf9SNathan Fontenot 		new_prop->value = kzalloc(new_prop->length, GFP_KERNEL);
110410bccf9SNathan Fontenot 		if (!new_prop->value) {
111410bccf9SNathan Fontenot 			kfree(new_prop->name);
112410bccf9SNathan Fontenot 			kfree(new_prop);
113410bccf9SNathan Fontenot 			return -ENOMEM;
114410bccf9SNathan Fontenot 		}
115410bccf9SNathan Fontenot 
116410bccf9SNathan Fontenot 		memcpy(new_prop->value, value, vd);
117410bccf9SNathan Fontenot 		*prop = new_prop;
118410bccf9SNathan Fontenot 	}
119410bccf9SNathan Fontenot 
120410bccf9SNathan Fontenot 	if (!more) {
12179d1c712SNathan Fontenot 		of_update_property(dn, new_prop);
122d8e533b4STyrel Datwyler 		*prop = NULL;
123410bccf9SNathan Fontenot 	}
124410bccf9SNathan Fontenot 
125410bccf9SNathan Fontenot 	return 0;
126410bccf9SNathan Fontenot }
127410bccf9SNathan Fontenot 
128762ec157SNathan Fontenot static int update_dt_node(u32 phandle, s32 scope)
129410bccf9SNathan Fontenot {
130410bccf9SNathan Fontenot 	struct update_props_workarea *upwa;
131410bccf9SNathan Fontenot 	struct device_node *dn;
132410bccf9SNathan Fontenot 	struct property *prop = NULL;
133*638a405fSTyrel Datwyler 	int i, rc, rtas_rc;
134410bccf9SNathan Fontenot 	char *prop_data;
135410bccf9SNathan Fontenot 	char *rtas_buf;
136410bccf9SNathan Fontenot 	int update_properties_token;
1372e9b7b02SNathan Fontenot 	u32 vd;
138410bccf9SNathan Fontenot 
139410bccf9SNathan Fontenot 	update_properties_token = rtas_token("ibm,update-properties");
140410bccf9SNathan Fontenot 	if (update_properties_token == RTAS_UNKNOWN_SERVICE)
141410bccf9SNathan Fontenot 		return -EINVAL;
142410bccf9SNathan Fontenot 
143410bccf9SNathan Fontenot 	rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL);
144410bccf9SNathan Fontenot 	if (!rtas_buf)
145410bccf9SNathan Fontenot 		return -ENOMEM;
146410bccf9SNathan Fontenot 
147410bccf9SNathan Fontenot 	dn = of_find_node_by_phandle(phandle);
148410bccf9SNathan Fontenot 	if (!dn) {
149410bccf9SNathan Fontenot 		kfree(rtas_buf);
150410bccf9SNathan Fontenot 		return -ENOENT;
151410bccf9SNathan Fontenot 	}
152410bccf9SNathan Fontenot 
153410bccf9SNathan Fontenot 	upwa = (struct update_props_workarea *)&rtas_buf[0];
154410bccf9SNathan Fontenot 	upwa->phandle = phandle;
155410bccf9SNathan Fontenot 
156410bccf9SNathan Fontenot 	do {
157*638a405fSTyrel Datwyler 		rtas_rc = mobility_rtas_call(update_properties_token, rtas_buf,
158762ec157SNathan Fontenot 					scope);
159*638a405fSTyrel Datwyler 		if (rtas_rc < 0)
160410bccf9SNathan Fontenot 			break;
161410bccf9SNathan Fontenot 
162410bccf9SNathan Fontenot 		prop_data = rtas_buf + sizeof(*upwa);
163410bccf9SNathan Fontenot 
1642e9b7b02SNathan Fontenot 		/* The first element of the buffer is the path of the node
1652e9b7b02SNathan Fontenot 		 * being updated in the form of a 8 byte string length
1662e9b7b02SNathan Fontenot 		 * followed by the string. Skip past this to get to the
1672e9b7b02SNathan Fontenot 		 * properties being updated.
1682e9b7b02SNathan Fontenot 		 */
169410bccf9SNathan Fontenot 		vd = *prop_data++;
1702e9b7b02SNathan Fontenot 		prop_data += vd;
1712e9b7b02SNathan Fontenot 
1722e9b7b02SNathan Fontenot 		/* The path we skipped over is counted as one of the elements
1732e9b7b02SNathan Fontenot 		 * returned so start counting at one.
1742e9b7b02SNathan Fontenot 		 */
1752e9b7b02SNathan Fontenot 		for (i = 1; i < upwa->nprops; i++) {
1762e9b7b02SNathan Fontenot 			char *prop_name;
1772e9b7b02SNathan Fontenot 
1782e9b7b02SNathan Fontenot 			prop_name = prop_data;
1792e9b7b02SNathan Fontenot 			prop_data += strlen(prop_name) + 1;
1802e9b7b02SNathan Fontenot 			vd = *(u32 *)prop_data;
1812e9b7b02SNathan Fontenot 			prop_data += sizeof(vd);
182410bccf9SNathan Fontenot 
183410bccf9SNathan Fontenot 			switch (vd) {
184410bccf9SNathan Fontenot 			case 0x00000000:
185410bccf9SNathan Fontenot 				/* name only property, nothing to do */
186410bccf9SNathan Fontenot 				break;
187410bccf9SNathan Fontenot 
188410bccf9SNathan Fontenot 			case 0x80000000:
189410bccf9SNathan Fontenot 				prop = of_find_property(dn, prop_name, NULL);
19079d1c712SNathan Fontenot 				of_remove_property(dn, prop);
191410bccf9SNathan Fontenot 				prop = NULL;
192410bccf9SNathan Fontenot 				break;
193410bccf9SNathan Fontenot 
194410bccf9SNathan Fontenot 			default:
195410bccf9SNathan Fontenot 				rc = update_dt_property(dn, &prop, prop_name,
196410bccf9SNathan Fontenot 							vd, prop_data);
197410bccf9SNathan Fontenot 				if (rc) {
198410bccf9SNathan Fontenot 					printk(KERN_ERR "Could not update %s"
199410bccf9SNathan Fontenot 					       " property\n", prop_name);
200410bccf9SNathan Fontenot 				}
201410bccf9SNathan Fontenot 
202410bccf9SNathan Fontenot 				prop_data += vd;
203410bccf9SNathan Fontenot 			}
204410bccf9SNathan Fontenot 		}
205*638a405fSTyrel Datwyler 	} while (rtas_rc == 1);
206410bccf9SNathan Fontenot 
207410bccf9SNathan Fontenot 	of_node_put(dn);
208410bccf9SNathan Fontenot 	kfree(rtas_buf);
209410bccf9SNathan Fontenot 	return 0;
210410bccf9SNathan Fontenot }
211410bccf9SNathan Fontenot 
212410bccf9SNathan Fontenot static int add_dt_node(u32 parent_phandle, u32 drc_index)
213410bccf9SNathan Fontenot {
214410bccf9SNathan Fontenot 	struct device_node *dn;
215410bccf9SNathan Fontenot 	struct device_node *parent_dn;
216410bccf9SNathan Fontenot 	int rc;
217410bccf9SNathan Fontenot 
218410bccf9SNathan Fontenot 	dn = dlpar_configure_connector(drc_index);
219410bccf9SNathan Fontenot 	if (!dn)
220410bccf9SNathan Fontenot 		return -ENOENT;
221410bccf9SNathan Fontenot 
222410bccf9SNathan Fontenot 	parent_dn = of_find_node_by_phandle(parent_phandle);
223410bccf9SNathan Fontenot 	if (!parent_dn) {
224410bccf9SNathan Fontenot 		dlpar_free_cc_nodes(dn);
225410bccf9SNathan Fontenot 		return -ENOENT;
226410bccf9SNathan Fontenot 	}
227410bccf9SNathan Fontenot 
228410bccf9SNathan Fontenot 	dn->parent = parent_dn;
229410bccf9SNathan Fontenot 	rc = dlpar_attach_node(dn);
230410bccf9SNathan Fontenot 	if (rc)
231410bccf9SNathan Fontenot 		dlpar_free_cc_nodes(dn);
232410bccf9SNathan Fontenot 
233410bccf9SNathan Fontenot 	of_node_put(parent_dn);
234410bccf9SNathan Fontenot 	return rc;
235410bccf9SNathan Fontenot }
236410bccf9SNathan Fontenot 
237762ec157SNathan Fontenot int pseries_devicetree_update(s32 scope)
238410bccf9SNathan Fontenot {
239410bccf9SNathan Fontenot 	char *rtas_buf;
240410bccf9SNathan Fontenot 	u32 *data;
241410bccf9SNathan Fontenot 	int update_nodes_token;
242410bccf9SNathan Fontenot 	int rc;
243410bccf9SNathan Fontenot 
244410bccf9SNathan Fontenot 	update_nodes_token = rtas_token("ibm,update-nodes");
245410bccf9SNathan Fontenot 	if (update_nodes_token == RTAS_UNKNOWN_SERVICE)
246410bccf9SNathan Fontenot 		return -EINVAL;
247410bccf9SNathan Fontenot 
248410bccf9SNathan Fontenot 	rtas_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL);
249410bccf9SNathan Fontenot 	if (!rtas_buf)
250410bccf9SNathan Fontenot 		return -ENOMEM;
251410bccf9SNathan Fontenot 
252410bccf9SNathan Fontenot 	do {
253762ec157SNathan Fontenot 		rc = mobility_rtas_call(update_nodes_token, rtas_buf, scope);
254410bccf9SNathan Fontenot 		if (rc && rc != 1)
255410bccf9SNathan Fontenot 			break;
256410bccf9SNathan Fontenot 
257410bccf9SNathan Fontenot 		data = (u32 *)rtas_buf + 4;
258410bccf9SNathan Fontenot 		while (*data & NODE_ACTION_MASK) {
259410bccf9SNathan Fontenot 			int i;
260410bccf9SNathan Fontenot 			u32 action = *data & NODE_ACTION_MASK;
261410bccf9SNathan Fontenot 			int node_count = *data & NODE_COUNT_MASK;
262410bccf9SNathan Fontenot 
263410bccf9SNathan Fontenot 			data++;
264410bccf9SNathan Fontenot 
265410bccf9SNathan Fontenot 			for (i = 0; i < node_count; i++) {
266410bccf9SNathan Fontenot 				u32 phandle = *data++;
267410bccf9SNathan Fontenot 				u32 drc_index;
268410bccf9SNathan Fontenot 
269410bccf9SNathan Fontenot 				switch (action) {
270410bccf9SNathan Fontenot 				case DELETE_DT_NODE:
271410bccf9SNathan Fontenot 					delete_dt_node(phandle);
272410bccf9SNathan Fontenot 					break;
273410bccf9SNathan Fontenot 				case UPDATE_DT_NODE:
274762ec157SNathan Fontenot 					update_dt_node(phandle, scope);
275410bccf9SNathan Fontenot 					break;
276410bccf9SNathan Fontenot 				case ADD_DT_NODE:
277410bccf9SNathan Fontenot 					drc_index = *data++;
278410bccf9SNathan Fontenot 					add_dt_node(phandle, drc_index);
279410bccf9SNathan Fontenot 					break;
280410bccf9SNathan Fontenot 				}
281410bccf9SNathan Fontenot 			}
282410bccf9SNathan Fontenot 		}
283410bccf9SNathan Fontenot 	} while (rc == 1);
284410bccf9SNathan Fontenot 
285410bccf9SNathan Fontenot 	kfree(rtas_buf);
286410bccf9SNathan Fontenot 	return rc;
287410bccf9SNathan Fontenot }
288410bccf9SNathan Fontenot 
289410bccf9SNathan Fontenot void post_mobility_fixup(void)
290410bccf9SNathan Fontenot {
291410bccf9SNathan Fontenot 	int rc;
292410bccf9SNathan Fontenot 	int activate_fw_token;
293410bccf9SNathan Fontenot 
294762ec157SNathan Fontenot 	rc = pseries_devicetree_update(MIGRATION_SCOPE);
295410bccf9SNathan Fontenot 	if (rc) {
296410bccf9SNathan Fontenot 		printk(KERN_ERR "Initial post-mobility device tree update "
297410bccf9SNathan Fontenot 		       "failed: %d\n", rc);
298410bccf9SNathan Fontenot 		return;
299410bccf9SNathan Fontenot 	}
300410bccf9SNathan Fontenot 
301410bccf9SNathan Fontenot 	activate_fw_token = rtas_token("ibm,activate-firmware");
302410bccf9SNathan Fontenot 	if (activate_fw_token == RTAS_UNKNOWN_SERVICE) {
303410bccf9SNathan Fontenot 		printk(KERN_ERR "Could not make post-mobility "
304410bccf9SNathan Fontenot 		       "activate-fw call.\n");
305410bccf9SNathan Fontenot 		return;
306410bccf9SNathan Fontenot 	}
307410bccf9SNathan Fontenot 
308410bccf9SNathan Fontenot 	rc = rtas_call(activate_fw_token, 0, 1, NULL);
309410bccf9SNathan Fontenot 	if (!rc) {
310762ec157SNathan Fontenot 		rc = pseries_devicetree_update(MIGRATION_SCOPE);
311410bccf9SNathan Fontenot 		if (rc)
312410bccf9SNathan Fontenot 			printk(KERN_ERR "Secondary post-mobility device tree "
313410bccf9SNathan Fontenot 			       "update failed: %d\n", rc);
314410bccf9SNathan Fontenot 	} else {
315410bccf9SNathan Fontenot 		printk(KERN_ERR "Post-mobility activate-fw failed: %d\n", rc);
316410bccf9SNathan Fontenot 		return;
317410bccf9SNathan Fontenot 	}
318410bccf9SNathan Fontenot 
319410bccf9SNathan Fontenot 	return;
320410bccf9SNathan Fontenot }
321410bccf9SNathan Fontenot 
322410bccf9SNathan Fontenot static ssize_t migrate_store(struct class *class, struct class_attribute *attr,
323410bccf9SNathan Fontenot 			     const char *buf, size_t count)
324410bccf9SNathan Fontenot {
325410bccf9SNathan Fontenot 	struct rtas_args args;
326410bccf9SNathan Fontenot 	u64 streamid;
327410bccf9SNathan Fontenot 	int rc;
328410bccf9SNathan Fontenot 
329410bccf9SNathan Fontenot 	rc = strict_strtoull(buf, 0, &streamid);
330410bccf9SNathan Fontenot 	if (rc)
331410bccf9SNathan Fontenot 		return rc;
332410bccf9SNathan Fontenot 
333410bccf9SNathan Fontenot 	memset(&args, 0, sizeof(args));
334410bccf9SNathan Fontenot 	args.token = rtas_token("ibm,suspend-me");
335410bccf9SNathan Fontenot 	args.nargs = 2;
336410bccf9SNathan Fontenot 	args.nret = 1;
337410bccf9SNathan Fontenot 
338410bccf9SNathan Fontenot 	args.args[0] = streamid >> 32 ;
339410bccf9SNathan Fontenot 	args.args[1] = streamid & 0xffffffff;
340410bccf9SNathan Fontenot 	args.rets = &args.args[args.nargs];
341410bccf9SNathan Fontenot 
342410bccf9SNathan Fontenot 	do {
343410bccf9SNathan Fontenot 		args.rets[0] = 0;
344410bccf9SNathan Fontenot 		rc = rtas_ibm_suspend_me(&args);
345410bccf9SNathan Fontenot 		if (!rc && args.rets[0] == RTAS_NOT_SUSPENDABLE)
346410bccf9SNathan Fontenot 			ssleep(1);
347410bccf9SNathan Fontenot 	} while (!rc && args.rets[0] == RTAS_NOT_SUSPENDABLE);
348410bccf9SNathan Fontenot 
349410bccf9SNathan Fontenot 	if (rc)
350410bccf9SNathan Fontenot 		return rc;
351410bccf9SNathan Fontenot 	else if (args.rets[0])
352410bccf9SNathan Fontenot 		return args.rets[0];
353410bccf9SNathan Fontenot 
354410bccf9SNathan Fontenot 	post_mobility_fixup();
355410bccf9SNathan Fontenot 	return count;
356410bccf9SNathan Fontenot }
357410bccf9SNathan Fontenot 
358410bccf9SNathan Fontenot static CLASS_ATTR(migration, S_IWUSR, NULL, migrate_store);
359410bccf9SNathan Fontenot 
360410bccf9SNathan Fontenot static int __init mobility_sysfs_init(void)
361410bccf9SNathan Fontenot {
362410bccf9SNathan Fontenot 	int rc;
363410bccf9SNathan Fontenot 
364410bccf9SNathan Fontenot 	mobility_kobj = kobject_create_and_add("mobility", kernel_kobj);
365410bccf9SNathan Fontenot 	if (!mobility_kobj)
366410bccf9SNathan Fontenot 		return -ENOMEM;
367410bccf9SNathan Fontenot 
368410bccf9SNathan Fontenot 	rc = sysfs_create_file(mobility_kobj, &class_attr_migration.attr);
369410bccf9SNathan Fontenot 
370410bccf9SNathan Fontenot 	return rc;
371410bccf9SNathan Fontenot }
372410bccf9SNathan Fontenot device_initcall(mobility_sysfs_init);
373