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 */
28static
29unsigned 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 */
50static
51unsigned 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 */
85static
86unsigned 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
115enum idlestate_value {
116	IDLESTATE_USAGE,
117	IDLESTATE_POWER,
118	IDLESTATE_LATENCY,
119	IDLESTATE_TIME,
120	IDLESTATE_DISABLE,
121	MAX_IDLESTATE_VALUE_FILES
122};
123
124static 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
132static
133unsigned 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
161enum idlestate_string {
162	IDLESTATE_DESC,
163	IDLESTATE_NAME,
164	MAX_IDLESTATE_STRING_FILES
165};
166
167static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = {
168	[IDLESTATE_DESC] = "desc",
169	[IDLESTATE_NAME] = "name",
170};
171
172
173static 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 */
207int 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 */
228int 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
251unsigned 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
257unsigned 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
263unsigned 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
269char *cpuidle_state_name(unsigned int cpu, unsigned int idlestate)
270{
271	return cpuidle_state_get_one_string(cpu, idlestate, IDLESTATE_NAME);
272}
273
274char *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 */
284unsigned 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 */
314static 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
328enum cpuidle_string {
329	CPUIDLE_GOVERNOR,
330	CPUIDLE_GOVERNOR_RO,
331	CPUIDLE_DRIVER,
332	MAX_CPUIDLE_STRING_FILES
333};
334
335static 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
342static 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
366char *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
375char *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