1/*
2 *  (C) 2004-2009  Dominik Brodowski <linux@dominikbrodowski.de>
3 *  (C) 2011       Thomas Renninger <trenn@novell.com> Novell Inc.
4 *
5 *  Licensed under the terms of the GNU GPL License version 2.
6 */
7
8#include <stdio.h>
9#include <errno.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/types.h>
13#include <sys/stat.h>
14#include <fcntl.h>
15#include <unistd.h>
16
17#include "helpers/sysfs.h"
18
19unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen)
20{
21	int fd;
22	ssize_t numread;
23
24	fd = open(path, O_RDONLY);
25	if (fd == -1)
26		return 0;
27
28	numread = read(fd, buf, buflen - 1);
29	if (numread < 1) {
30		close(fd);
31		return 0;
32	}
33
34	buf[numread] = '\0';
35	close(fd);
36
37	return (unsigned int) numread;
38}
39
40/*
41 * Detect whether a CPU is online
42 *
43 * Returns:
44 *     1 -> if CPU is online
45 *     0 -> if CPU is offline
46 *     negative errno values in error case
47 */
48int sysfs_is_cpu_online(unsigned int cpu)
49{
50	char path[SYSFS_PATH_MAX];
51	int fd;
52	ssize_t numread;
53	unsigned long long value;
54	char linebuf[MAX_LINE_LEN];
55	char *endp;
56	struct stat statbuf;
57
58	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u", cpu);
59
60	if (stat(path, &statbuf) != 0)
61		return 0;
62
63	/*
64	 * kernel without CONFIG_HOTPLUG_CPU
65	 * -> cpuX directory exists, but not cpuX/online file
66	 */
67	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/online", cpu);
68	if (stat(path, &statbuf) != 0)
69		return 1;
70
71	fd = open(path, O_RDONLY);
72	if (fd == -1)
73		return -errno;
74
75	numread = read(fd, linebuf, MAX_LINE_LEN - 1);
76	if (numread < 1) {
77		close(fd);
78		return -EIO;
79	}
80	linebuf[numread] = '\0';
81	close(fd);
82
83	value = strtoull(linebuf, &endp, 0);
84	if (value > 1)
85		return -EINVAL;
86
87	return value;
88}
89
90/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
91
92
93/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
94
95/*
96 * helper function to check whether a file under "../cpuX/cpuidle/stateX/" dir
97 * exists.
98 * For example the functionality to disable c-states was introduced in later
99 * kernel versions, this function can be used to explicitly check for this
100 * feature.
101 *
102 * returns 1 if the file exists, 0 otherwise.
103 */
104unsigned int sysfs_idlestate_file_exists(unsigned int cpu,
105					 unsigned int idlestate,
106					 const char *fname)
107{
108	char path[SYSFS_PATH_MAX];
109	struct stat statbuf;
110
111
112	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
113		 cpu, idlestate, fname);
114	if (stat(path, &statbuf) != 0)
115		return 0;
116	return 1;
117}
118
119/*
120 * helper function to read file from /sys into given buffer
121 * fname is a relative path under "cpuX/cpuidle/stateX/" dir
122 * cstates starting with 0, C0 is not counted as cstate.
123 * This means if you want C1 info, pass 0 as idlestate param
124 */
125unsigned int sysfs_idlestate_read_file(unsigned int cpu, unsigned int idlestate,
126			     const char *fname, char *buf, size_t buflen)
127{
128	char path[SYSFS_PATH_MAX];
129	int fd;
130	ssize_t numread;
131
132	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
133		 cpu, idlestate, fname);
134
135	fd = open(path, O_RDONLY);
136	if (fd == -1)
137		return 0;
138
139	numread = read(fd, buf, buflen - 1);
140	if (numread < 1) {
141		close(fd);
142		return 0;
143	}
144
145	buf[numread] = '\0';
146	close(fd);
147
148	return (unsigned int) numread;
149}
150
151/*
152 * helper function to write a new value to a /sys file
153 * fname is a relative path under "../cpuX/cpuidle/cstateY/" dir
154 *
155 * Returns the number of bytes written or 0 on error
156 */
157static
158unsigned int sysfs_idlestate_write_file(unsigned int cpu,
159					unsigned int idlestate,
160					const char *fname,
161					const char *value, size_t len)
162{
163	char path[SYSFS_PATH_MAX];
164	int fd;
165	ssize_t numwrite;
166
167	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
168		 cpu, idlestate, fname);
169
170	fd = open(path, O_WRONLY);
171	if (fd == -1)
172		return 0;
173
174	numwrite = write(fd, value, len);
175	if (numwrite < 1) {
176		close(fd);
177		return 0;
178	}
179
180	close(fd);
181
182	return (unsigned int) numwrite;
183}
184
185/* read access to files which contain one numeric value */
186
187enum idlestate_value {
188	IDLESTATE_USAGE,
189	IDLESTATE_POWER,
190	IDLESTATE_LATENCY,
191	IDLESTATE_TIME,
192	IDLESTATE_DISABLE,
193	MAX_IDLESTATE_VALUE_FILES
194};
195
196static const char *idlestate_value_files[MAX_IDLESTATE_VALUE_FILES] = {
197	[IDLESTATE_USAGE] = "usage",
198	[IDLESTATE_POWER] = "power",
199	[IDLESTATE_LATENCY] = "latency",
200	[IDLESTATE_TIME]  = "time",
201	[IDLESTATE_DISABLE]  = "disable",
202};
203
204static unsigned long long sysfs_idlestate_get_one_value(unsigned int cpu,
205						     unsigned int idlestate,
206						     enum idlestate_value which)
207{
208	unsigned long long value;
209	unsigned int len;
210	char linebuf[MAX_LINE_LEN];
211	char *endp;
212
213	if (which >= MAX_IDLESTATE_VALUE_FILES)
214		return 0;
215
216	len = sysfs_idlestate_read_file(cpu, idlestate,
217					idlestate_value_files[which],
218					linebuf, sizeof(linebuf));
219	if (len == 0)
220		return 0;
221
222	value = strtoull(linebuf, &endp, 0);
223
224	if (endp == linebuf || errno == ERANGE)
225		return 0;
226
227	return value;
228}
229
230/* read access to files which contain one string */
231
232enum idlestate_string {
233	IDLESTATE_DESC,
234	IDLESTATE_NAME,
235	MAX_IDLESTATE_STRING_FILES
236};
237
238static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = {
239	[IDLESTATE_DESC] = "desc",
240	[IDLESTATE_NAME] = "name",
241};
242
243
244static char *sysfs_idlestate_get_one_string(unsigned int cpu,
245					unsigned int idlestate,
246					enum idlestate_string which)
247{
248	char linebuf[MAX_LINE_LEN];
249	char *result;
250	unsigned int len;
251
252	if (which >= MAX_IDLESTATE_STRING_FILES)
253		return NULL;
254
255	len = sysfs_idlestate_read_file(cpu, idlestate,
256					idlestate_string_files[which],
257					linebuf, sizeof(linebuf));
258	if (len == 0)
259		return NULL;
260
261	result = strdup(linebuf);
262	if (result == NULL)
263		return NULL;
264
265	if (result[strlen(result) - 1] == '\n')
266		result[strlen(result) - 1] = '\0';
267
268	return result;
269}
270
271/*
272 * Returns:
273 *    1  if disabled
274 *    0  if enabled
275 *    -1 if idlestate is not available
276 *    -2 if disabling is not supported by the kernel
277 */
278int sysfs_is_idlestate_disabled(unsigned int cpu,
279				unsigned int idlestate)
280{
281	if (sysfs_get_idlestate_count(cpu) <= idlestate)
282		return -1;
283
284	if (!sysfs_idlestate_file_exists(cpu, idlestate,
285				 idlestate_value_files[IDLESTATE_DISABLE]))
286		return -2;
287	return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_DISABLE);
288}
289
290/*
291 * Pass 1 as last argument to disable or 0 to enable the state
292 * Returns:
293 *    0  on success
294 *    negative values on error, for example:
295 *      -1 if idlestate is not available
296 *      -2 if disabling is not supported by the kernel
297 *      -3 No write access to disable/enable C-states
298 */
299int sysfs_idlestate_disable(unsigned int cpu,
300			    unsigned int idlestate,
301			    unsigned int disable)
302{
303	char value[SYSFS_PATH_MAX];
304	int bytes_written;
305
306	if (sysfs_get_idlestate_count(cpu) <= idlestate)
307		return -1;
308
309	if (!sysfs_idlestate_file_exists(cpu, idlestate,
310				 idlestate_value_files[IDLESTATE_DISABLE]))
311		return -2;
312
313	snprintf(value, SYSFS_PATH_MAX, "%u", disable);
314
315	bytes_written = sysfs_idlestate_write_file(cpu, idlestate, "disable",
316						   value, sizeof(disable));
317	if (bytes_written)
318		return 0;
319	return -3;
320}
321
322unsigned long sysfs_get_idlestate_latency(unsigned int cpu,
323					  unsigned int idlestate)
324{
325	return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_LATENCY);
326}
327
328unsigned long sysfs_get_idlestate_usage(unsigned int cpu,
329					unsigned int idlestate)
330{
331	return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_USAGE);
332}
333
334unsigned long long sysfs_get_idlestate_time(unsigned int cpu,
335					unsigned int idlestate)
336{
337	return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_TIME);
338}
339
340char *sysfs_get_idlestate_name(unsigned int cpu, unsigned int idlestate)
341{
342	return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_NAME);
343}
344
345char *sysfs_get_idlestate_desc(unsigned int cpu, unsigned int idlestate)
346{
347	return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_DESC);
348}
349
350/*
351 * Returns number of supported C-states of CPU core cpu
352 * Negativ in error case
353 * Zero if cpuidle does not export any C-states
354 */
355unsigned int sysfs_get_idlestate_count(unsigned int cpu)
356{
357	char file[SYSFS_PATH_MAX];
358	struct stat statbuf;
359	int idlestates = 1;
360
361
362	snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpuidle");
363	if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
364		return 0;
365
366	snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpu%u/cpuidle/state0", cpu);
367	if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
368		return 0;
369
370	while (stat(file, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
371		snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU
372			 "cpu%u/cpuidle/state%d", cpu, idlestates);
373		idlestates++;
374	}
375	idlestates--;
376	return idlestates;
377}
378
379/* CPUidle general /sys/devices/system/cpu/cpuidle/ sysfs access ********/
380
381/*
382 * helper function to read file from /sys into given buffer
383 * fname is a relative path under "cpu/cpuidle/" dir
384 */
385static unsigned int sysfs_cpuidle_read_file(const char *fname, char *buf,
386					    size_t buflen)
387{
388	char path[SYSFS_PATH_MAX];
389
390	snprintf(path, sizeof(path), PATH_TO_CPU "cpuidle/%s", fname);
391
392	return sysfs_read_file(path, buf, buflen);
393}
394
395
396
397/* read access to files which contain one string */
398
399enum cpuidle_string {
400	CPUIDLE_GOVERNOR,
401	CPUIDLE_GOVERNOR_RO,
402	CPUIDLE_DRIVER,
403	MAX_CPUIDLE_STRING_FILES
404};
405
406static const char *cpuidle_string_files[MAX_CPUIDLE_STRING_FILES] = {
407	[CPUIDLE_GOVERNOR]	= "current_governor",
408	[CPUIDLE_GOVERNOR_RO]	= "current_governor_ro",
409	[CPUIDLE_DRIVER]	= "current_driver",
410};
411
412
413static char *sysfs_cpuidle_get_one_string(enum cpuidle_string which)
414{
415	char linebuf[MAX_LINE_LEN];
416	char *result;
417	unsigned int len;
418
419	if (which >= MAX_CPUIDLE_STRING_FILES)
420		return NULL;
421
422	len = sysfs_cpuidle_read_file(cpuidle_string_files[which],
423				linebuf, sizeof(linebuf));
424	if (len == 0)
425		return NULL;
426
427	result = strdup(linebuf);
428	if (result == NULL)
429		return NULL;
430
431	if (result[strlen(result) - 1] == '\n')
432		result[strlen(result) - 1] = '\0';
433
434	return result;
435}
436
437char *sysfs_get_cpuidle_governor(void)
438{
439	char *tmp = sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR_RO);
440	if (!tmp)
441		return sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR);
442	else
443		return tmp;
444}
445
446char *sysfs_get_cpuidle_driver(void)
447{
448	return sysfs_cpuidle_get_one_string(CPUIDLE_DRIVER);
449}
450/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
451
452/*
453 * Get sched_mc or sched_smt settings
454 * Pass "mc" or "smt" as argument
455 *
456 * Returns negative value on failure
457 */
458int sysfs_get_sched(const char *smt_mc)
459{
460	return -ENODEV;
461}
462
463/*
464 * Get sched_mc or sched_smt settings
465 * Pass "mc" or "smt" as argument
466 *
467 * Returns negative value on failure
468 */
469int sysfs_set_sched(const char *smt_mc, int val)
470{
471	return -ENODEV;
472}
473