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