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> | |
8a25a2fd | 18 | #include <linux/device.h> |
5742bd85 VS |
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> | |
0388c79c | 24 | #include <asm/firmware.h> |
5742bd85 VS |
25 | |
26 | ||
27 | #define MODULE_VERS "1.0" | |
28 | #define MODULE_NAME "pseries_energy" | |
29 | ||
30 | /* Driver flags */ | |
31 | ||
32 | static int sysfs_entries; | |
33 | ||
34 | /* Helper routines */ | |
35 | ||
5742bd85 VS |
36 | /* Helper Routines to convert between drc_index to cpu numbers */ |
37 | ||
38 | static u32 cpu_to_drc_index(int cpu) | |
39 | { | |
40 | struct device_node *dn = NULL; | |
41 | const int *indexes; | |
42 | int i; | |
43 | int rc = 1; | |
44 | u32 ret = 0; | |
45 | ||
46 | dn = of_find_node_by_path("/cpus"); | |
47 | if (dn == NULL) | |
48 | goto err; | |
49 | indexes = of_get_property(dn, "ibm,drc-indexes", NULL); | |
50 | if (indexes == NULL) | |
51 | goto err_of_node_put; | |
52 | /* Convert logical cpu number to core number */ | |
53 | i = cpu_core_index_of_thread(cpu); | |
54 | /* | |
55 | * The first element indexes[0] is the number of drc_indexes | |
56 | * returned in the list. Hence i+1 will get the drc_index | |
57 | * corresponding to core number i. | |
58 | */ | |
59 | WARN_ON(i > indexes[0]); | |
60 | ret = indexes[i + 1]; | |
61 | rc = 0; | |
62 | ||
63 | err_of_node_put: | |
64 | of_node_put(dn); | |
65 | err: | |
66 | if (rc) | |
67 | printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu); | |
68 | return ret; | |
69 | } | |
70 | ||
71 | static int drc_index_to_cpu(u32 drc_index) | |
72 | { | |
73 | struct device_node *dn = NULL; | |
74 | const int *indexes; | |
75 | int i, cpu = 0; | |
76 | int rc = 1; | |
77 | ||
78 | dn = of_find_node_by_path("/cpus"); | |
79 | if (dn == NULL) | |
80 | goto err; | |
81 | indexes = of_get_property(dn, "ibm,drc-indexes", NULL); | |
82 | if (indexes == NULL) | |
83 | goto err_of_node_put; | |
84 | /* | |
85 | * First element in the array is the number of drc_indexes | |
86 | * returned. Search through the list to find the matching | |
87 | * drc_index and get the core number | |
88 | */ | |
89 | for (i = 0; i < indexes[0]; i++) { | |
90 | if (indexes[i + 1] == drc_index) | |
91 | break; | |
92 | } | |
93 | /* Convert core number to logical cpu number */ | |
94 | cpu = cpu_first_thread_of_core(i); | |
95 | rc = 0; | |
96 | ||
97 | err_of_node_put: | |
98 | of_node_put(dn); | |
99 | err: | |
100 | if (rc) | |
101 | printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index); | |
102 | return cpu; | |
103 | } | |
104 | ||
105 | /* | |
106 | * pseries hypervisor call H_BEST_ENERGY provides hints to OS on | |
107 | * preferred logical cpus to activate or deactivate for optimized | |
108 | * energy consumption. | |
109 | */ | |
110 | ||
b0d436c7 AB |
111 | #define FLAGS_MODE1 0x004E200000080E01UL |
112 | #define FLAGS_MODE2 0x004E200000080401UL | |
5742bd85 VS |
113 | #define FLAGS_ACTIVATE 0x100 |
114 | ||
115 | static ssize_t get_best_energy_list(char *page, int activate) | |
116 | { | |
117 | int rc, cnt, i, cpu; | |
118 | unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; | |
119 | unsigned long flags = 0; | |
120 | u32 *buf_page; | |
121 | char *s = page; | |
122 | ||
123 | buf_page = (u32 *) get_zeroed_page(GFP_KERNEL); | |
124 | if (!buf_page) | |
125 | return -ENOMEM; | |
126 | ||
127 | flags = FLAGS_MODE1; | |
128 | if (activate) | |
129 | flags |= FLAGS_ACTIVATE; | |
130 | ||
131 | rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page), | |
132 | 0, 0, 0, 0, 0, 0); | |
133 | if (rc != H_SUCCESS) { | |
134 | free_page((unsigned long) buf_page); | |
135 | return -EINVAL; | |
136 | } | |
137 | ||
138 | cnt = retbuf[0]; | |
139 | for (i = 0; i < cnt; i++) { | |
140 | cpu = drc_index_to_cpu(buf_page[2*i+1]); | |
141 | if ((cpu_online(cpu) && !activate) || | |
142 | (!cpu_online(cpu) && activate)) | |
143 | s += sprintf(s, "%d,", cpu); | |
144 | } | |
145 | if (s > page) { /* Something to show */ | |
146 | s--; /* Suppress last comma */ | |
147 | s += sprintf(s, "\n"); | |
148 | } | |
149 | ||
150 | free_page((unsigned long) buf_page); | |
151 | return s-page; | |
152 | } | |
153 | ||
8a25a2fd | 154 | static ssize_t get_best_energy_data(struct device *dev, |
5742bd85 VS |
155 | char *page, int activate) |
156 | { | |
157 | int rc; | |
158 | unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; | |
159 | unsigned long flags = 0; | |
160 | ||
161 | flags = FLAGS_MODE2; | |
162 | if (activate) | |
163 | flags |= FLAGS_ACTIVATE; | |
164 | ||
165 | rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, | |
166 | cpu_to_drc_index(dev->id), | |
167 | 0, 0, 0, 0, 0, 0, 0); | |
168 | ||
169 | if (rc != H_SUCCESS) | |
170 | return -EINVAL; | |
171 | ||
172 | return sprintf(page, "%lu\n", retbuf[1] >> 32); | |
173 | } | |
174 | ||
175 | /* Wrapper functions */ | |
176 | ||
8a25a2fd KS |
177 | static ssize_t cpu_activate_hint_list_show(struct device *dev, |
178 | struct device_attribute *attr, char *page) | |
5742bd85 VS |
179 | { |
180 | return get_best_energy_list(page, 1); | |
181 | } | |
182 | ||
8a25a2fd KS |
183 | static ssize_t cpu_deactivate_hint_list_show(struct device *dev, |
184 | struct device_attribute *attr, char *page) | |
5742bd85 VS |
185 | { |
186 | return get_best_energy_list(page, 0); | |
187 | } | |
188 | ||
8a25a2fd KS |
189 | static ssize_t percpu_activate_hint_show(struct device *dev, |
190 | struct device_attribute *attr, char *page) | |
5742bd85 VS |
191 | { |
192 | return get_best_energy_data(dev, page, 1); | |
193 | } | |
194 | ||
8a25a2fd KS |
195 | static ssize_t percpu_deactivate_hint_show(struct device *dev, |
196 | struct device_attribute *attr, char *page) | |
5742bd85 VS |
197 | { |
198 | return get_best_energy_data(dev, page, 0); | |
199 | } | |
200 | ||
201 | /* | |
202 | * Create sysfs interface: | |
203 | * /sys/devices/system/cpu/pseries_activate_hint_list | |
204 | * /sys/devices/system/cpu/pseries_deactivate_hint_list | |
205 | * Comma separated list of cpus to activate or deactivate | |
206 | * /sys/devices/system/cpu/cpuN/pseries_activate_hint | |
207 | * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint | |
208 | * Per-cpu value of the hint | |
209 | */ | |
210 | ||
8a25a2fd KS |
211 | struct device_attribute attr_cpu_activate_hint_list = |
212 | __ATTR(pseries_activate_hint_list, 0444, | |
5742bd85 VS |
213 | cpu_activate_hint_list_show, NULL); |
214 | ||
8a25a2fd KS |
215 | struct device_attribute attr_cpu_deactivate_hint_list = |
216 | __ATTR(pseries_deactivate_hint_list, 0444, | |
5742bd85 VS |
217 | cpu_deactivate_hint_list_show, NULL); |
218 | ||
8a25a2fd KS |
219 | struct device_attribute attr_percpu_activate_hint = |
220 | __ATTR(pseries_activate_hint, 0444, | |
5742bd85 VS |
221 | percpu_activate_hint_show, NULL); |
222 | ||
8a25a2fd KS |
223 | struct device_attribute attr_percpu_deactivate_hint = |
224 | __ATTR(pseries_deactivate_hint, 0444, | |
5742bd85 VS |
225 | percpu_deactivate_hint_show, NULL); |
226 | ||
227 | static int __init pseries_energy_init(void) | |
228 | { | |
229 | int cpu, err; | |
8a25a2fd | 230 | struct device *cpu_dev; |
5742bd85 | 231 | |
0388c79c | 232 | if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY)) { |
5742bd85 VS |
233 | printk(KERN_INFO "Hypercall H_BEST_ENERGY not supported\n"); |
234 | return 0; | |
235 | } | |
236 | /* Create the sysfs files */ | |
8a25a2fd KS |
237 | err = device_create_file(cpu_subsys.dev_root, |
238 | &attr_cpu_activate_hint_list); | |
5742bd85 | 239 | if (!err) |
8a25a2fd KS |
240 | err = device_create_file(cpu_subsys.dev_root, |
241 | &attr_cpu_deactivate_hint_list); | |
5742bd85 VS |
242 | |
243 | if (err) | |
244 | return err; | |
245 | for_each_possible_cpu(cpu) { | |
8a25a2fd KS |
246 | cpu_dev = get_cpu_device(cpu); |
247 | err = device_create_file(cpu_dev, | |
248 | &attr_percpu_activate_hint); | |
5742bd85 VS |
249 | if (err) |
250 | break; | |
8a25a2fd KS |
251 | err = device_create_file(cpu_dev, |
252 | &attr_percpu_deactivate_hint); | |
5742bd85 VS |
253 | if (err) |
254 | break; | |
255 | } | |
256 | ||
257 | if (err) | |
258 | return err; | |
259 | ||
260 | sysfs_entries = 1; /* Removed entries on cleanup */ | |
261 | return 0; | |
262 | ||
263 | } | |
264 | ||
265 | static void __exit pseries_energy_cleanup(void) | |
266 | { | |
267 | int cpu; | |
8a25a2fd | 268 | struct device *cpu_dev; |
5742bd85 VS |
269 | |
270 | if (!sysfs_entries) | |
271 | return; | |
272 | ||
273 | /* Remove the sysfs files */ | |
8a25a2fd KS |
274 | device_remove_file(cpu_subsys.dev_root, &attr_cpu_activate_hint_list); |
275 | device_remove_file(cpu_subsys.dev_root, &attr_cpu_deactivate_hint_list); | |
5742bd85 VS |
276 | |
277 | for_each_possible_cpu(cpu) { | |
8a25a2fd KS |
278 | cpu_dev = get_cpu_device(cpu); |
279 | sysfs_remove_file(&cpu_dev->kobj, | |
5742bd85 | 280 | &attr_percpu_activate_hint.attr); |
8a25a2fd | 281 | sysfs_remove_file(&cpu_dev->kobj, |
5742bd85 VS |
282 | &attr_percpu_deactivate_hint.attr); |
283 | } | |
284 | } | |
285 | ||
286 | module_init(pseries_energy_init); | |
287 | module_exit(pseries_energy_cleanup); | |
288 | MODULE_DESCRIPTION("Driver for pSeries platform energy management"); | |
289 | MODULE_AUTHOR("Vaidyanathan Srinivasan"); | |
290 | MODULE_LICENSE("GPL"); |