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 
19 unsigned 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  */
48 int 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 || value < 0)
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  */
104 unsigned 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  */
125 unsigned 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  */
157 static
158 unsigned 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 
187 enum idlestate_value {
188 	IDLESTATE_USAGE,
189 	IDLESTATE_POWER,
190 	IDLESTATE_LATENCY,
191 	IDLESTATE_TIME,
192 	IDLESTATE_DISABLE,
193 	MAX_IDLESTATE_VALUE_FILES
194 };
195 
196 static 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 
204 static 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 
232 enum idlestate_string {
233 	IDLESTATE_DESC,
234 	IDLESTATE_NAME,
235 	MAX_IDLESTATE_STRING_FILES
236 };
237 
238 static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = {
239 	[IDLESTATE_DESC] = "desc",
240 	[IDLESTATE_NAME] = "name",
241 };
242 
243 
244 static 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  */
278 int 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  */
299 int 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 
322 unsigned 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 
328 unsigned 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 
334 unsigned 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 
340 char *sysfs_get_idlestate_name(unsigned int cpu, unsigned int idlestate)
341 {
342 	return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_NAME);
343 }
344 
345 char *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  */
355 unsigned 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 -ENODEV;
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  */
385 static 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 
399 enum cpuidle_string {
400 	CPUIDLE_GOVERNOR,
401 	CPUIDLE_GOVERNOR_RO,
402 	CPUIDLE_DRIVER,
403 	MAX_CPUIDLE_STRING_FILES
404 };
405 
406 static 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 
413 static 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 
437 char *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 
446 char *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  */
458 int 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  */
469 int sysfs_set_sched(const char *smt_mc, int val)
470 {
471 	return -ENODEV;
472 }
473