Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/arch/powerpc/platforms/pseries/pseries_energy.c
10818 views
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");
327
328