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