xref: /openbmc/linux/tools/power/cpupower/lib/cpuidle.c (revision 03638e62)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  *  (C) 2004-2009  Dominik Brodowski <linux@dominikbrodowski.de>
4  *  (C) 2011       Thomas Renninger <trenn@novell.com> Novell Inc.
5  */
6 
7 #include <stdio.h>
8 #include <errno.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 
16 #include "cpuidle.h"
17 #include "cpupower_intern.h"
18 
19 /*
20  * helper function to check whether a file under "../cpuX/cpuidle/stateX/" dir
21  * exists.
22  * For example the functionality to disable c-states was introduced in later
23  * kernel versions, this function can be used to explicitly check for this
24  * feature.
25  *
26  * returns 1 if the file exists, 0 otherwise.
27  */
28 static
29 unsigned int cpuidle_state_file_exists(unsigned int cpu,
30 				       unsigned int idlestate,
31 				       const char *fname)
32 {
33 	char path[SYSFS_PATH_MAX];
34 	struct stat statbuf;
35 
36 
37 	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
38 		 cpu, idlestate, fname);
39 	if (stat(path, &statbuf) != 0)
40 		return 0;
41 	return 1;
42 }
43 
44 /*
45  * helper function to read file from /sys into given buffer
46  * fname is a relative path under "cpuX/cpuidle/stateX/" dir
47  * cstates starting with 0, C0 is not counted as cstate.
48  * This means if you want C1 info, pass 0 as idlestate param
49  */
50 static
51 unsigned int cpuidle_state_read_file(unsigned int cpu,
52 					    unsigned int idlestate,
53 					    const char *fname, char *buf,
54 					    size_t buflen)
55 {
56 	char path[SYSFS_PATH_MAX];
57 	int fd;
58 	ssize_t numread;
59 
60 	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
61 		 cpu, idlestate, fname);
62 
63 	fd = open(path, O_RDONLY);
64 	if (fd == -1)
65 		return 0;
66 
67 	numread = read(fd, buf, buflen - 1);
68 	if (numread < 1) {
69 		close(fd);
70 		return 0;
71 	}
72 
73 	buf[numread] = '\0';
74 	close(fd);
75 
76 	return (unsigned int) numread;
77 }
78 
79 /*
80  * helper function to write a new value to a /sys file
81  * fname is a relative path under "../cpuX/cpuidle/cstateY/" dir
82  *
83  * Returns the number of bytes written or 0 on error
84  */
85 static
86 unsigned int cpuidle_state_write_file(unsigned int cpu,
87 				      unsigned int idlestate,
88 				      const char *fname,
89 				      const char *value, size_t len)
90 {
91 	char path[SYSFS_PATH_MAX];
92 	int fd;
93 	ssize_t numwrite;
94 
95 	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
96 		 cpu, idlestate, fname);
97 
98 	fd = open(path, O_WRONLY);
99 	if (fd == -1)
100 		return 0;
101 
102 	numwrite = write(fd, value, len);
103 	if (numwrite < 1) {
104 		close(fd);
105 		return 0;
106 	}
107 
108 	close(fd);
109 
110 	return (unsigned int) numwrite;
111 }
112 
113 /* read access to files which contain one numeric value */
114 
115 enum idlestate_value {
116 	IDLESTATE_USAGE,
117 	IDLESTATE_POWER,
118 	IDLESTATE_LATENCY,
119 	IDLESTATE_TIME,
120 	IDLESTATE_DISABLE,
121 	MAX_IDLESTATE_VALUE_FILES
122 };
123 
124 static const char *idlestate_value_files[MAX_IDLESTATE_VALUE_FILES] = {
125 	[IDLESTATE_USAGE] = "usage",
126 	[IDLESTATE_POWER] = "power",
127 	[IDLESTATE_LATENCY] = "latency",
128 	[IDLESTATE_TIME]  = "time",
129 	[IDLESTATE_DISABLE]  = "disable",
130 };
131 
132 static
133 unsigned long long cpuidle_state_get_one_value(unsigned int cpu,
134 					       unsigned int idlestate,
135 					       enum idlestate_value which)
136 {
137 	unsigned long long value;
138 	unsigned int len;
139 	char linebuf[MAX_LINE_LEN];
140 	char *endp;
141 
142 	if (which >= MAX_IDLESTATE_VALUE_FILES)
143 		return 0;
144 
145 	len = cpuidle_state_read_file(cpu, idlestate,
146 				      idlestate_value_files[which],
147 				      linebuf, sizeof(linebuf));
148 	if (len == 0)
149 		return 0;
150 
151 	value = strtoull(linebuf, &endp, 0);
152 
153 	if (endp == linebuf || errno == ERANGE)
154 		return 0;
155 
156 	return value;
157 }
158 
159 /* read access to files which contain one string */
160 
161 enum idlestate_string {
162 	IDLESTATE_DESC,
163 	IDLESTATE_NAME,
164 	MAX_IDLESTATE_STRING_FILES
165 };
166 
167 static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = {
168 	[IDLESTATE_DESC] = "desc",
169 	[IDLESTATE_NAME] = "name",
170 };
171 
172 
173 static char *cpuidle_state_get_one_string(unsigned int cpu,
174 					unsigned int idlestate,
175 					enum idlestate_string which)
176 {
177 	char linebuf[MAX_LINE_LEN];
178 	char *result;
179 	unsigned int len;
180 
181 	if (which >= MAX_IDLESTATE_STRING_FILES)
182 		return NULL;
183 
184 	len = cpuidle_state_read_file(cpu, idlestate,
185 				      idlestate_string_files[which],
186 				      linebuf, sizeof(linebuf));
187 	if (len == 0)
188 		return NULL;
189 
190 	result = strdup(linebuf);
191 	if (result == NULL)
192 		return NULL;
193 
194 	if (result[strlen(result) - 1] == '\n')
195 		result[strlen(result) - 1] = '\0';
196 
197 	return result;
198 }
199 
200 /*
201  * Returns:
202  *    1  if disabled
203  *    0  if enabled
204  *    -1 if idlestate is not available
205  *    -2 if disabling is not supported by the kernel
206  */
207 int cpuidle_is_state_disabled(unsigned int cpu,
208 				unsigned int idlestate)
209 {
210 	if (cpuidle_state_count(cpu) <= idlestate)
211 		return -1;
212 
213 	if (!cpuidle_state_file_exists(cpu, idlestate,
214 				 idlestate_value_files[IDLESTATE_DISABLE]))
215 		return -2;
216 	return cpuidle_state_get_one_value(cpu, idlestate, IDLESTATE_DISABLE);
217 }
218 
219 /*
220  * Pass 1 as last argument to disable or 0 to enable the state
221  * Returns:
222  *    0  on success
223  *    negative values on error, for example:
224  *      -1 if idlestate is not available
225  *      -2 if disabling is not supported by the kernel
226  *      -3 No write access to disable/enable C-states
227  */
228 int cpuidle_state_disable(unsigned int cpu,
229 			    unsigned int idlestate,
230 			    unsigned int disable)
231 {
232 	char value[SYSFS_PATH_MAX];
233 	int bytes_written;
234 
235 	if (cpuidle_state_count(cpu) <= idlestate)
236 		return -1;
237 
238 	if (!cpuidle_state_file_exists(cpu, idlestate,
239 				 idlestate_value_files[IDLESTATE_DISABLE]))
240 		return -2;
241 
242 	snprintf(value, SYSFS_PATH_MAX, "%u", disable);
243 
244 	bytes_written = cpuidle_state_write_file(cpu, idlestate, "disable",
245 						   value, sizeof(disable));
246 	if (bytes_written)
247 		return 0;
248 	return -3;
249 }
250 
251 unsigned long cpuidle_state_latency(unsigned int cpu,
252 					  unsigned int idlestate)
253 {
254 	return cpuidle_state_get_one_value(cpu, idlestate, IDLESTATE_LATENCY);
255 }
256 
257 unsigned long cpuidle_state_usage(unsigned int cpu,
258 					unsigned int idlestate)
259 {
260 	return cpuidle_state_get_one_value(cpu, idlestate, IDLESTATE_USAGE);
261 }
262 
263 unsigned long long cpuidle_state_time(unsigned int cpu,
264 					unsigned int idlestate)
265 {
266 	return cpuidle_state_get_one_value(cpu, idlestate, IDLESTATE_TIME);
267 }
268 
269 char *cpuidle_state_name(unsigned int cpu, unsigned int idlestate)
270 {
271 	return cpuidle_state_get_one_string(cpu, idlestate, IDLESTATE_NAME);
272 }
273 
274 char *cpuidle_state_desc(unsigned int cpu, unsigned int idlestate)
275 {
276 	return cpuidle_state_get_one_string(cpu, idlestate, IDLESTATE_DESC);
277 }
278 
279 /*
280  * Returns number of supported C-states of CPU core cpu
281  * Negativ in error case
282  * Zero if cpuidle does not export any C-states
283  */
284 unsigned int cpuidle_state_count(unsigned int cpu)
285 {
286 	char file[SYSFS_PATH_MAX];
287 	struct stat statbuf;
288 	int idlestates = 1;
289 
290 
291 	snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpuidle");
292 	if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
293 		return 0;
294 
295 	snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpu%u/cpuidle/state0", cpu);
296 	if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
297 		return 0;
298 
299 	while (stat(file, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
300 		snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU
301 			 "cpu%u/cpuidle/state%d", cpu, idlestates);
302 		idlestates++;
303 	}
304 	idlestates--;
305 	return idlestates;
306 }
307 
308 /* CPUidle general /sys/devices/system/cpu/cpuidle/ sysfs access ********/
309 
310 /*
311  * helper function to read file from /sys into given buffer
312  * fname is a relative path under "cpu/cpuidle/" dir
313  */
314 static unsigned int sysfs_cpuidle_read_file(const char *fname, char *buf,
315 					    size_t buflen)
316 {
317 	char path[SYSFS_PATH_MAX];
318 
319 	snprintf(path, sizeof(path), PATH_TO_CPU "cpuidle/%s", fname);
320 
321 	return cpupower_read_sysfs(path, buf, buflen);
322 }
323 
324 
325 
326 /* read access to files which contain one string */
327 
328 enum cpuidle_string {
329 	CPUIDLE_GOVERNOR,
330 	CPUIDLE_GOVERNOR_RO,
331 	CPUIDLE_DRIVER,
332 	MAX_CPUIDLE_STRING_FILES
333 };
334 
335 static const char *cpuidle_string_files[MAX_CPUIDLE_STRING_FILES] = {
336 	[CPUIDLE_GOVERNOR]	= "current_governor",
337 	[CPUIDLE_GOVERNOR_RO]	= "current_governor_ro",
338 	[CPUIDLE_DRIVER]	= "current_driver",
339 };
340 
341 
342 static char *sysfs_cpuidle_get_one_string(enum cpuidle_string which)
343 {
344 	char linebuf[MAX_LINE_LEN];
345 	char *result;
346 	unsigned int len;
347 
348 	if (which >= MAX_CPUIDLE_STRING_FILES)
349 		return NULL;
350 
351 	len = sysfs_cpuidle_read_file(cpuidle_string_files[which],
352 				linebuf, sizeof(linebuf));
353 	if (len == 0)
354 		return NULL;
355 
356 	result = strdup(linebuf);
357 	if (result == NULL)
358 		return NULL;
359 
360 	if (result[strlen(result) - 1] == '\n')
361 		result[strlen(result) - 1] = '\0';
362 
363 	return result;
364 }
365 
366 char *cpuidle_get_governor(void)
367 {
368 	char *tmp = sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR_RO);
369 	if (!tmp)
370 		return sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR);
371 	else
372 		return tmp;
373 }
374 
375 char *cpuidle_get_driver(void)
376 {
377 	return sysfs_cpuidle_get_one_string(CPUIDLE_DRIVER);
378 }
379 /* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
380