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