Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/arch/powerpc/platforms/pseries/pseries_energy.c
26481 views
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("/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(dn, "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(info, NULL, &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(dn, "ibm,drc-indexes",
85
0, &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(dn, "ibm,drc-indexes",
91
thread_index + 1,
92
&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(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("/cpus");
118
if (dn == NULL)
119
goto err;
120
info = of_find_property(dn, "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(info, NULL, &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(dn, "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(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(buf_page[2*i+1]);
214
if ((cpu_online(cpu) && !activate) ||
215
(!cpu_online(cpu) && activate))
216
s += sprintf(s, "%d,", cpu);
217
}
218
if (s > page) { /* Something to show */
219
s--; /* Suppress last comma */
220
s += sprintf(s, "\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(dev->id),
240
0, 0, 0, 0, 0, 0, 0);
241
242
if (rc != H_SUCCESS)
243
return -EINVAL;
244
245
return sprintf(page, "%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, 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, 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, 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, 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(&cpu_subsys);
310
if (dev_root) {
311
err = device_create_file(dev_root, &attr_cpu_activate_hint_list);
312
if (!err)
313
err = device_create_file(dev_root, &attr_cpu_deactivate_hint_list);
314
put_device(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(cpu_dev,
322
&attr_percpu_activate_hint);
323
if (err)
324
break;
325
err = device_create_file(cpu_dev,
326
&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(&cpu_subsys);
349
if (dev_root) {
350
device_remove_file(dev_root, &attr_cpu_activate_hint_list);
351
device_remove_file(dev_root, &attr_cpu_deactivate_hint_list);
352
put_device(dev_root);
353
}
354
355
for_each_possible_cpu(cpu) {
356
cpu_dev = get_cpu_device(cpu);
357
sysfs_remove_file(&cpu_dev->kobj,
358
&attr_percpu_activate_hint.attr);
359
sysfs_remove_file(&cpu_dev->kobj,
360
&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
370