Commit | Line | Data |
---|---|---|
5742bd85 VS |
1 | /* |
2 | * POWER platform energy management driver | |
3 | * Copyright (C) 2010 IBM Corporation | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or | |
6 | * modify it under the terms of the GNU General Public License | |
7 | * version 2 as published by the Free Software Foundation. | |
8 | * | |
9 | * This pseries platform device driver provides access to | |
10 | * platform energy management capabilities. | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/types.h> | |
15 | #include <linux/errno.h> | |
16 | #include <linux/init.h> | |
17 | #include <linux/seq_file.h> | |
18 | #include <linux/sysdev.h> | |
19 | #include <linux/cpu.h> | |
20 | #include <linux/of.h> | |
21 | #include <asm/cputhreads.h> | |
22 | #include <asm/page.h> | |
23 | #include <asm/hvcall.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 | /* | |
36 | * Routine to detect firmware support for hcall | |
37 | * return 1 if H_BEST_ENERGY is supported | |
38 | * else return 0 | |
39 | */ | |
40 | ||
41 | static int check_for_h_best_energy(void) | |
42 | { | |
43 | struct device_node *rtas = NULL; | |
44 | const char *hypertas, *s; | |
45 | int length; | |
46 | int rc = 0; | |
47 | ||
48 | rtas = of_find_node_by_path("/rtas"); | |
49 | if (!rtas) | |
50 | return 0; | |
51 | ||
52 | hypertas = of_get_property(rtas, "ibm,hypertas-functions", &length); | |
53 | if (!hypertas) { | |
54 | of_node_put(rtas); | |
55 | return 0; | |
56 | } | |
57 | ||
58 | /* hypertas will have list of strings with hcall names */ | |
59 | for (s = hypertas; s < hypertas + length; s += strlen(s) + 1) { | |
60 | if (!strncmp("hcall-best-energy-1", s, 19)) { | |
61 | rc = 1; /* Found the string */ | |
62 | break; | |
63 | } | |
64 | } | |
65 | of_node_put(rtas); | |
66 | return rc; | |
67 | } | |
68 | ||
69 | /* Helper Routines to convert between drc_index to cpu numbers */ | |
70 | ||
71 | static u32 cpu_to_drc_index(int cpu) | |
72 | { | |
73 | struct device_node *dn = NULL; | |
74 | const int *indexes; | |
75 | int i; | |
76 | int rc = 1; | |
77 | u32 ret = 0; | |
78 | ||
79 | dn = of_find_node_by_path("/cpus"); | |
80 | if (dn == NULL) | |
81 | goto err; | |
82 | indexes = of_get_property(dn, "ibm,drc-indexes", NULL); | |
83 | if (indexes == NULL) | |
84 | goto err_of_node_put; | |
85 | /* Convert logical cpu number to core number */ | |
86 | i = cpu_core_index_of_thread(cpu); | |
87 | /* | |
88 | * The first element indexes[0] is the number of drc_indexes | |
89 | * returned in the list. Hence i+1 will get the drc_index | |
90 | * corresponding to core number i. | |
91 | */ | |
92 | WARN_ON(i > indexes[0]); | |
93 | ret = indexes[i + 1]; | |
94 | rc = 0; | |
95 | ||
96 | err_of_node_put: | |
97 | of_node_put(dn); | |
98 | err: | |
99 | if (rc) | |
100 | printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu); | |
101 | return ret; | |
102 | } | |
103 | ||
104 | static int drc_index_to_cpu(u32 drc_index) | |
105 | { | |
106 | struct device_node *dn = NULL; | |
107 | const int *indexes; | |
108 | int i, cpu = 0; | |
109 | int rc = 1; | |
110 | ||
111 | dn = of_find_node_by_path("/cpus"); | |
112 | if (dn == NULL) | |
113 | goto err; | |
114 | indexes = of_get_property(dn, "ibm,drc-indexes", NULL); | |
115 | if (indexes == NULL) | |
116 | goto err_of_node_put; | |
117 | /* | |
118 | * First element in the array is the number of drc_indexes | |
119 | * returned. Search through the list to find the matching | |
120 | * drc_index and get the core number | |
121 | */ | |
122 | for (i = 0; i < indexes[0]; i++) { | |
123 | if (indexes[i + 1] == drc_index) | |
124 | break; | |
125 | } | |
126 | /* Convert core number to logical cpu number */ | |
127 | cpu = cpu_first_thread_of_core(i); | |
128 | rc = 0; | |
129 | ||
130 | err_of_node_put: | |
131 | of_node_put(dn); | |
132 | err: | |
133 | if (rc) | |
134 | printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index); | |
135 | return cpu; | |
136 | } | |
137 | ||
138 | /* | |
139 | * pseries hypervisor call H_BEST_ENERGY provides hints to OS on | |
140 | * preferred logical cpus to activate or deactivate for optimized | |
141 | * energy consumption. | |
142 | */ | |
143 | ||
144 | #define FLAGS_MODE1 0x004E200000080E01 | |
145 | #define FLAGS_MODE2 0x004E200000080401 | |
146 | #define FLAGS_ACTIVATE 0x100 | |
147 | ||
148 | static ssize_t get_best_energy_list(char *page, int activate) | |
149 | { | |
150 | int rc, cnt, i, cpu; | |
151 | unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; | |
152 | unsigned long flags = 0; | |
153 | u32 *buf_page; | |
154 | char *s = page; | |
155 | ||
156 | buf_page = (u32 *) get_zeroed_page(GFP_KERNEL); | |
157 | if (!buf_page) | |
158 | return -ENOMEM; | |
159 | ||
160 | flags = FLAGS_MODE1; | |
161 | if (activate) | |
162 | flags |= FLAGS_ACTIVATE; | |
163 | ||
164 | rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page), | |
165 | 0, 0, 0, 0, 0, 0); | |
166 | if (rc != H_SUCCESS) { | |
167 | free_page((unsigned long) buf_page); | |
168 | return -EINVAL; | |
169 | } | |
170 | ||
171 | cnt = retbuf[0]; | |
172 | for (i = 0; i < cnt; i++) { | |
173 | cpu = drc_index_to_cpu(buf_page[2*i+1]); | |
174 | if ((cpu_online(cpu) && !activate) || | |
175 | (!cpu_online(cpu) && activate)) | |
176 | s += sprintf(s, "%d,", cpu); | |
177 | } | |
178 | if (s > page) { /* Something to show */ | |
179 | s--; /* Suppress last comma */ | |
180 | s += sprintf(s, "\n"); | |
181 | } | |
182 | ||
183 | free_page((unsigned long) buf_page); | |
184 | return s-page; | |
185 | } | |
186 | ||
187 | static ssize_t get_best_energy_data(struct sys_device *dev, | |
188 | char *page, int activate) | |
189 | { | |
190 | int rc; | |
191 | unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; | |
192 | unsigned long flags = 0; | |
193 | ||
194 | flags = FLAGS_MODE2; | |
195 | if (activate) | |
196 | flags |= FLAGS_ACTIVATE; | |
197 | ||
198 | rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, | |
199 | cpu_to_drc_index(dev->id), | |
200 | 0, 0, 0, 0, 0, 0, 0); | |
201 | ||
202 | if (rc != H_SUCCESS) | |
203 | return -EINVAL; | |
204 | ||
205 | return sprintf(page, "%lu\n", retbuf[1] >> 32); | |
206 | } | |
207 | ||
208 | /* Wrapper functions */ | |
209 | ||
210 | static ssize_t cpu_activate_hint_list_show(struct sysdev_class *class, | |
211 | struct sysdev_class_attribute *attr, char *page) | |
212 | { | |
213 | return get_best_energy_list(page, 1); | |
214 | } | |
215 | ||
216 | static ssize_t cpu_deactivate_hint_list_show(struct sysdev_class *class, | |
217 | struct sysdev_class_attribute *attr, char *page) | |
218 | { | |
219 | return get_best_energy_list(page, 0); | |
220 | } | |
221 | ||
222 | static ssize_t percpu_activate_hint_show(struct sys_device *dev, | |
223 | struct sysdev_attribute *attr, char *page) | |
224 | { | |
225 | return get_best_energy_data(dev, page, 1); | |
226 | } | |
227 | ||
228 | static ssize_t percpu_deactivate_hint_show(struct sys_device *dev, | |
229 | struct sysdev_attribute *attr, char *page) | |
230 | { | |
231 | return get_best_energy_data(dev, page, 0); | |
232 | } | |
233 | ||
234 | /* | |
235 | * Create sysfs interface: | |
236 | * /sys/devices/system/cpu/pseries_activate_hint_list | |
237 | * /sys/devices/system/cpu/pseries_deactivate_hint_list | |
238 | * Comma separated list of cpus to activate or deactivate | |
239 | * /sys/devices/system/cpu/cpuN/pseries_activate_hint | |
240 | * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint | |
241 | * Per-cpu value of the hint | |
242 | */ | |
243 | ||
244 | struct sysdev_class_attribute attr_cpu_activate_hint_list = | |
245 | _SYSDEV_CLASS_ATTR(pseries_activate_hint_list, 0444, | |
246 | cpu_activate_hint_list_show, NULL); | |
247 | ||
248 | struct sysdev_class_attribute attr_cpu_deactivate_hint_list = | |
249 | _SYSDEV_CLASS_ATTR(pseries_deactivate_hint_list, 0444, | |
250 | cpu_deactivate_hint_list_show, NULL); | |
251 | ||
252 | struct sysdev_attribute attr_percpu_activate_hint = | |
253 | _SYSDEV_ATTR(pseries_activate_hint, 0444, | |
254 | percpu_activate_hint_show, NULL); | |
255 | ||
256 | struct sysdev_attribute attr_percpu_deactivate_hint = | |
257 | _SYSDEV_ATTR(pseries_deactivate_hint, 0444, | |
258 | percpu_deactivate_hint_show, NULL); | |
259 | ||
260 | static int __init pseries_energy_init(void) | |
261 | { | |
262 | int cpu, err; | |
263 | struct sys_device *cpu_sys_dev; | |
264 | ||
265 | if (!check_for_h_best_energy()) { | |
266 | printk(KERN_INFO "Hypercall H_BEST_ENERGY not supported\n"); | |
267 | return 0; | |
268 | } | |
269 | /* Create the sysfs files */ | |
270 | err = sysfs_create_file(&cpu_sysdev_class.kset.kobj, | |
271 | &attr_cpu_activate_hint_list.attr); | |
272 | if (!err) | |
273 | err = sysfs_create_file(&cpu_sysdev_class.kset.kobj, | |
274 | &attr_cpu_deactivate_hint_list.attr); | |
275 | ||
276 | if (err) | |
277 | return err; | |
278 | for_each_possible_cpu(cpu) { | |
279 | cpu_sys_dev = get_cpu_sysdev(cpu); | |
280 | err = sysfs_create_file(&cpu_sys_dev->kobj, | |
281 | &attr_percpu_activate_hint.attr); | |
282 | if (err) | |
283 | break; | |
284 | err = sysfs_create_file(&cpu_sys_dev->kobj, | |
285 | &attr_percpu_deactivate_hint.attr); | |
286 | if (err) | |
287 | break; | |
288 | } | |
289 | ||
290 | if (err) | |
291 | return err; | |
292 | ||
293 | sysfs_entries = 1; /* Removed entries on cleanup */ | |
294 | return 0; | |
295 | ||
296 | } | |
297 | ||
298 | static void __exit pseries_energy_cleanup(void) | |
299 | { | |
300 | int cpu; | |
301 | struct sys_device *cpu_sys_dev; | |
302 | ||
303 | if (!sysfs_entries) | |
304 | return; | |
305 | ||
306 | /* Remove the sysfs files */ | |
307 | sysfs_remove_file(&cpu_sysdev_class.kset.kobj, | |
308 | &attr_cpu_activate_hint_list.attr); | |
309 | ||
310 | sysfs_remove_file(&cpu_sysdev_class.kset.kobj, | |
311 | &attr_cpu_deactivate_hint_list.attr); | |
312 | ||
313 | for_each_possible_cpu(cpu) { | |
314 | cpu_sys_dev = get_cpu_sysdev(cpu); | |
315 | sysfs_remove_file(&cpu_sys_dev->kobj, | |
316 | &attr_percpu_activate_hint.attr); | |
317 | sysfs_remove_file(&cpu_sys_dev->kobj, | |
318 | &attr_percpu_deactivate_hint.attr); | |
319 | } | |
320 | } | |
321 | ||
322 | module_init(pseries_energy_init); | |
323 | module_exit(pseries_energy_cleanup); | |
324 | MODULE_DESCRIPTION("Driver for pSeries platform energy management"); | |
325 | MODULE_AUTHOR("Vaidyanathan Srinivasan"); | |
326 | MODULE_LICENSE("GPL"); |