xref: /linux/arch/powerpc/platforms/pseries/pseries_energy.c (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * POWER platform energy management driver
4  * Copyright (C) 2010 IBM Corporation
5  *
6  * This pseries platform device driver provides access to
7  * platform energy management capabilities.
8  */
9 
10 #include <linux/module.h>
11 #include <linux/types.h>
12 #include <linux/errno.h>
13 #include <linux/init.h>
14 #include <linux/seq_file.h>
15 #include <linux/sysfs.h>
16 #include <linux/device.h>
17 #include <linux/cpu.h>
18 #include <linux/of.h>
19 #include <asm/cputhreads.h>
20 #include <asm/page.h>
21 #include <asm/hvcall.h>
22 #include <asm/firmware.h>
23 #include <asm/prom.h>
24 
25 
26 #define MODULE_VERS "1.0"
27 #define MODULE_NAME "pseries_energy"
28 
29 /* Driver flags */
30 
31 static int sysfs_entries;
32 
33 /* Helper routines */
34 
35 /* Helper Routines to convert between drc_index to cpu numbers */
36 
37 static u32 cpu_to_drc_index(int cpu)
38 {
39 	struct device_node *dn = NULL;
40 	struct property *info;
41 	int thread_index;
42 	int rc = 1;
43 	u32 ret = 0;
44 
45 	dn = of_find_node_by_path("/cpus");
46 	if (dn == NULL)
47 		goto err;
48 
49 	/* Convert logical cpu number to core number */
50 	thread_index = cpu_core_index_of_thread(cpu);
51 
52 	info = of_find_property(dn, "ibm,drc-info", NULL);
53 	if (info) {
54 		struct of_drc_info drc;
55 		int j;
56 		u32 num_set_entries;
57 		const __be32 *value;
58 
59 		value = of_prop_next_u32(info, NULL, &num_set_entries);
60 		if (!value)
61 			goto err_of_node_put;
62 		else
63 			value++;
64 
65 		for (j = 0; j < num_set_entries; j++) {
66 
67 			of_read_drc_info_cell(&info, &value, &drc);
68 			if (strncmp(drc.drc_type, "CPU", 3))
69 				goto err;
70 
71 			if (thread_index < drc.last_drc_index)
72 				break;
73 		}
74 
75 		ret = drc.drc_index_start + (thread_index * drc.sequential_inc);
76 	} else {
77 		u32 nr_drc_indexes, thread_drc_index;
78 
79 		/*
80 		 * The first element of ibm,drc-indexes array is the
81 		 * number of drc_indexes returned in the list.  Hence
82 		 * thread_index+1 will get the drc_index corresponding
83 		 * to core number thread_index.
84 		 */
85 		rc = of_property_read_u32_index(dn, "ibm,drc-indexes",
86 						0, &nr_drc_indexes);
87 		if (rc)
88 			goto err_of_node_put;
89 
90 		WARN_ON_ONCE(thread_index > nr_drc_indexes);
91 		rc = of_property_read_u32_index(dn, "ibm,drc-indexes",
92 						thread_index + 1,
93 						&thread_drc_index);
94 		if (rc)
95 			goto err_of_node_put;
96 
97 		ret = thread_drc_index;
98 	}
99 
100 	rc = 0;
101 
102 err_of_node_put:
103 	of_node_put(dn);
104 err:
105 	if (rc)
106 		printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu);
107 	return ret;
108 }
109 
110 static int drc_index_to_cpu(u32 drc_index)
111 {
112 	struct device_node *dn = NULL;
113 	struct property *info;
114 	const int *indexes;
115 	int thread_index = 0, cpu = 0;
116 	int rc = 1;
117 
118 	dn = of_find_node_by_path("/cpus");
119 	if (dn == NULL)
120 		goto err;
121 	info = of_find_property(dn, "ibm,drc-info", NULL);
122 	if (info) {
123 		struct of_drc_info drc;
124 		int j;
125 		u32 num_set_entries;
126 		const __be32 *value;
127 
128 		value = of_prop_next_u32(info, NULL, &num_set_entries);
129 		if (!value)
130 			goto err_of_node_put;
131 		else
132 			value++;
133 
134 		for (j = 0; j < num_set_entries; j++) {
135 
136 			of_read_drc_info_cell(&info, &value, &drc);
137 			if (strncmp(drc.drc_type, "CPU", 3))
138 				goto err;
139 
140 			if (drc_index > drc.last_drc_index) {
141 				cpu += drc.num_sequential_elems;
142 				continue;
143 			}
144 			cpu += ((drc_index - drc.drc_index_start) /
145 				drc.sequential_inc);
146 
147 			thread_index = cpu_first_thread_of_core(cpu);
148 			rc = 0;
149 			break;
150 		}
151 	} else {
152 		unsigned long int i;
153 
154 		indexes = of_get_property(dn, "ibm,drc-indexes", NULL);
155 		if (indexes == NULL)
156 			goto err_of_node_put;
157 		/*
158 		 * First element in the array is the number of drc_indexes
159 		 * returned.  Search through the list to find the matching
160 		 * drc_index and get the core number
161 		 */
162 		for (i = 0; i < indexes[0]; i++) {
163 			if (indexes[i + 1] == drc_index)
164 				break;
165 		}
166 		/* Convert core number to logical cpu number */
167 		thread_index = cpu_first_thread_of_core(i);
168 		rc = 0;
169 	}
170 
171 err_of_node_put:
172 	of_node_put(dn);
173 err:
174 	if (rc)
175 		printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index);
176 	return thread_index;
177 }
178 
179 /*
180  * pseries hypervisor call H_BEST_ENERGY provides hints to OS on
181  * preferred logical cpus to activate or deactivate for optimized
182  * energy consumption.
183  */
184 
185 #define FLAGS_MODE1	0x004E200000080E01UL
186 #define FLAGS_MODE2	0x004E200000080401UL
187 #define FLAGS_ACTIVATE  0x100
188 
189 static ssize_t get_best_energy_list(char *page, int activate)
190 {
191 	int rc, cnt, i, cpu;
192 	unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];
193 	unsigned long flags = 0;
194 	u32 *buf_page;
195 	char *s = page;
196 
197 	buf_page = (u32 *) get_zeroed_page(GFP_KERNEL);
198 	if (!buf_page)
199 		return -ENOMEM;
200 
201 	flags = FLAGS_MODE1;
202 	if (activate)
203 		flags |= FLAGS_ACTIVATE;
204 
205 	rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page),
206 				0, 0, 0, 0, 0, 0);
207 	if (rc != H_SUCCESS) {
208 		free_page((unsigned long) buf_page);
209 		return -EINVAL;
210 	}
211 
212 	cnt = retbuf[0];
213 	for (i = 0; i < cnt; i++) {
214 		cpu = drc_index_to_cpu(buf_page[2*i+1]);
215 		if ((cpu_online(cpu) && !activate) ||
216 		    (!cpu_online(cpu) && activate))
217 			s += sprintf(s, "%d,", cpu);
218 	}
219 	if (s > page) { /* Something to show */
220 		s--; /* Suppress last comma */
221 		s += sprintf(s, "\n");
222 	}
223 
224 	free_page((unsigned long) buf_page);
225 	return s-page;
226 }
227 
228 static ssize_t get_best_energy_data(struct device *dev,
229 					char *page, int activate)
230 {
231 	int rc;
232 	unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];
233 	unsigned long flags = 0;
234 
235 	flags = FLAGS_MODE2;
236 	if (activate)
237 		flags |= FLAGS_ACTIVATE;
238 
239 	rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags,
240 				cpu_to_drc_index(dev->id),
241 				0, 0, 0, 0, 0, 0, 0);
242 
243 	if (rc != H_SUCCESS)
244 		return -EINVAL;
245 
246 	return sysfs_emit(page, "%lu\n", retbuf[1] >> 32);
247 }
248 
249 /* Wrapper functions */
250 
251 static ssize_t cpu_activate_hint_list_show(struct device *dev,
252 			struct device_attribute *attr, char *page)
253 {
254 	return get_best_energy_list(page, 1);
255 }
256 
257 static ssize_t cpu_deactivate_hint_list_show(struct device *dev,
258 			struct device_attribute *attr, char *page)
259 {
260 	return get_best_energy_list(page, 0);
261 }
262 
263 static ssize_t percpu_activate_hint_show(struct device *dev,
264 			struct device_attribute *attr, char *page)
265 {
266 	return get_best_energy_data(dev, page, 1);
267 }
268 
269 static ssize_t percpu_deactivate_hint_show(struct device *dev,
270 			struct device_attribute *attr, char *page)
271 {
272 	return get_best_energy_data(dev, page, 0);
273 }
274 
275 /*
276  * Create sysfs interface:
277  * /sys/devices/system/cpu/pseries_activate_hint_list
278  * /sys/devices/system/cpu/pseries_deactivate_hint_list
279  *	Comma separated list of cpus to activate or deactivate
280  * /sys/devices/system/cpu/cpuN/pseries_activate_hint
281  * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint
282  *	Per-cpu value of the hint
283  */
284 
285 static struct device_attribute attr_cpu_activate_hint_list =
286 		__ATTR(pseries_activate_hint_list, 0444,
287 		cpu_activate_hint_list_show, NULL);
288 
289 static struct device_attribute attr_cpu_deactivate_hint_list =
290 		__ATTR(pseries_deactivate_hint_list, 0444,
291 		cpu_deactivate_hint_list_show, NULL);
292 
293 static struct device_attribute attr_percpu_activate_hint =
294 		__ATTR(pseries_activate_hint, 0444,
295 		percpu_activate_hint_show, NULL);
296 
297 static struct device_attribute attr_percpu_deactivate_hint =
298 		__ATTR(pseries_deactivate_hint, 0444,
299 		percpu_deactivate_hint_show, NULL);
300 
301 static int __init pseries_energy_init(void)
302 {
303 	int cpu, err;
304 	struct device *cpu_dev, *dev_root;
305 
306 	if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY))
307 		return 0; /* H_BEST_ENERGY hcall not supported */
308 
309 	/* Create the sysfs files */
310 	dev_root = bus_get_dev_root(&cpu_subsys);
311 	if (dev_root) {
312 		err = device_create_file(dev_root, &attr_cpu_activate_hint_list);
313 		if (!err)
314 			err = device_create_file(dev_root, &attr_cpu_deactivate_hint_list);
315 		put_device(dev_root);
316 		if (err)
317 			return err;
318 	}
319 
320 	for_each_possible_cpu(cpu) {
321 		cpu_dev = get_cpu_device(cpu);
322 		err = device_create_file(cpu_dev,
323 				&attr_percpu_activate_hint);
324 		if (err)
325 			break;
326 		err = device_create_file(cpu_dev,
327 				&attr_percpu_deactivate_hint);
328 		if (err)
329 			break;
330 	}
331 
332 	if (err)
333 		return err;
334 
335 	sysfs_entries = 1; /* Removed entries on cleanup */
336 	return 0;
337 
338 }
339 
340 static void __exit pseries_energy_cleanup(void)
341 {
342 	int cpu;
343 	struct device *cpu_dev, *dev_root;
344 
345 	if (!sysfs_entries)
346 		return;
347 
348 	/* Remove the sysfs files */
349 	dev_root = bus_get_dev_root(&cpu_subsys);
350 	if (dev_root) {
351 		device_remove_file(dev_root, &attr_cpu_activate_hint_list);
352 		device_remove_file(dev_root, &attr_cpu_deactivate_hint_list);
353 		put_device(dev_root);
354 	}
355 
356 	for_each_possible_cpu(cpu) {
357 		cpu_dev = get_cpu_device(cpu);
358 		sysfs_remove_file(&cpu_dev->kobj,
359 				&attr_percpu_activate_hint.attr);
360 		sysfs_remove_file(&cpu_dev->kobj,
361 				&attr_percpu_deactivate_hint.attr);
362 	}
363 }
364 
365 module_init(pseries_energy_init);
366 module_exit(pseries_energy_cleanup);
367 MODULE_DESCRIPTION("Driver for pSeries platform energy management");
368 MODULE_AUTHOR("Vaidyanathan Srinivasan");
369 MODULE_LICENSE("GPL");
370