forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
10 KiB
471 lines
10 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* (C) 2004-2009 Dominik Brodowski <[email protected]> |
|
* (C) 2011 Thomas Renninger <[email protected]> Novell Inc. |
|
*/ |
|
|
|
#include <stdio.h> |
|
#include <errno.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
#include <unistd.h> |
|
|
|
#include "helpers/sysfs.h" |
|
|
|
unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen) |
|
{ |
|
int fd; |
|
ssize_t numread; |
|
|
|
fd = open(path, O_RDONLY); |
|
if (fd == -1) |
|
return 0; |
|
|
|
numread = read(fd, buf, buflen - 1); |
|
if (numread < 1) { |
|
close(fd); |
|
return 0; |
|
} |
|
|
|
buf[numread] = '\0'; |
|
close(fd); |
|
|
|
return (unsigned int) numread; |
|
} |
|
|
|
/* |
|
* Detect whether a CPU is online |
|
* |
|
* Returns: |
|
* 1 -> if CPU is online |
|
* 0 -> if CPU is offline |
|
* negative errno values in error case |
|
*/ |
|
int sysfs_is_cpu_online(unsigned int cpu) |
|
{ |
|
char path[SYSFS_PATH_MAX]; |
|
int fd; |
|
ssize_t numread; |
|
unsigned long long value; |
|
char linebuf[MAX_LINE_LEN]; |
|
char *endp; |
|
struct stat statbuf; |
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u", cpu); |
|
|
|
if (stat(path, &statbuf) != 0) |
|
return 0; |
|
|
|
/* |
|
* kernel without CONFIG_HOTPLUG_CPU |
|
* -> cpuX directory exists, but not cpuX/online file |
|
*/ |
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/online", cpu); |
|
if (stat(path, &statbuf) != 0) |
|
return 1; |
|
|
|
fd = open(path, O_RDONLY); |
|
if (fd == -1) |
|
return -errno; |
|
|
|
numread = read(fd, linebuf, MAX_LINE_LEN - 1); |
|
if (numread < 1) { |
|
close(fd); |
|
return -EIO; |
|
} |
|
linebuf[numread] = '\0'; |
|
close(fd); |
|
|
|
value = strtoull(linebuf, &endp, 0); |
|
if (value > 1) |
|
return -EINVAL; |
|
|
|
return value; |
|
} |
|
|
|
/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */ |
|
|
|
|
|
/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */ |
|
|
|
/* |
|
* helper function to check whether a file under "../cpuX/cpuidle/stateX/" dir |
|
* exists. |
|
* For example the functionality to disable c-states was introduced in later |
|
* kernel versions, this function can be used to explicitly check for this |
|
* feature. |
|
* |
|
* returns 1 if the file exists, 0 otherwise. |
|
*/ |
|
unsigned int sysfs_idlestate_file_exists(unsigned int cpu, |
|
unsigned int idlestate, |
|
const char *fname) |
|
{ |
|
char path[SYSFS_PATH_MAX]; |
|
struct stat statbuf; |
|
|
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s", |
|
cpu, idlestate, fname); |
|
if (stat(path, &statbuf) != 0) |
|
return 0; |
|
return 1; |
|
} |
|
|
|
/* |
|
* helper function to read file from /sys into given buffer |
|
* fname is a relative path under "cpuX/cpuidle/stateX/" dir |
|
* cstates starting with 0, C0 is not counted as cstate. |
|
* This means if you want C1 info, pass 0 as idlestate param |
|
*/ |
|
unsigned int sysfs_idlestate_read_file(unsigned int cpu, unsigned int idlestate, |
|
const char *fname, char *buf, size_t buflen) |
|
{ |
|
char path[SYSFS_PATH_MAX]; |
|
int fd; |
|
ssize_t numread; |
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s", |
|
cpu, idlestate, fname); |
|
|
|
fd = open(path, O_RDONLY); |
|
if (fd == -1) |
|
return 0; |
|
|
|
numread = read(fd, buf, buflen - 1); |
|
if (numread < 1) { |
|
close(fd); |
|
return 0; |
|
} |
|
|
|
buf[numread] = '\0'; |
|
close(fd); |
|
|
|
return (unsigned int) numread; |
|
} |
|
|
|
/* |
|
* helper function to write a new value to a /sys file |
|
* fname is a relative path under "../cpuX/cpuidle/cstateY/" dir |
|
* |
|
* Returns the number of bytes written or 0 on error |
|
*/ |
|
static |
|
unsigned int sysfs_idlestate_write_file(unsigned int cpu, |
|
unsigned int idlestate, |
|
const char *fname, |
|
const char *value, size_t len) |
|
{ |
|
char path[SYSFS_PATH_MAX]; |
|
int fd; |
|
ssize_t numwrite; |
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s", |
|
cpu, idlestate, fname); |
|
|
|
fd = open(path, O_WRONLY); |
|
if (fd == -1) |
|
return 0; |
|
|
|
numwrite = write(fd, value, len); |
|
if (numwrite < 1) { |
|
close(fd); |
|
return 0; |
|
} |
|
|
|
close(fd); |
|
|
|
return (unsigned int) numwrite; |
|
} |
|
|
|
/* read access to files which contain one numeric value */ |
|
|
|
enum idlestate_value { |
|
IDLESTATE_USAGE, |
|
IDLESTATE_POWER, |
|
IDLESTATE_LATENCY, |
|
IDLESTATE_TIME, |
|
IDLESTATE_DISABLE, |
|
MAX_IDLESTATE_VALUE_FILES |
|
}; |
|
|
|
static const char *idlestate_value_files[MAX_IDLESTATE_VALUE_FILES] = { |
|
[IDLESTATE_USAGE] = "usage", |
|
[IDLESTATE_POWER] = "power", |
|
[IDLESTATE_LATENCY] = "latency", |
|
[IDLESTATE_TIME] = "time", |
|
[IDLESTATE_DISABLE] = "disable", |
|
}; |
|
|
|
static unsigned long long sysfs_idlestate_get_one_value(unsigned int cpu, |
|
unsigned int idlestate, |
|
enum idlestate_value which) |
|
{ |
|
unsigned long long value; |
|
unsigned int len; |
|
char linebuf[MAX_LINE_LEN]; |
|
char *endp; |
|
|
|
if (which >= MAX_IDLESTATE_VALUE_FILES) |
|
return 0; |
|
|
|
len = sysfs_idlestate_read_file(cpu, idlestate, |
|
idlestate_value_files[which], |
|
linebuf, sizeof(linebuf)); |
|
if (len == 0) |
|
return 0; |
|
|
|
value = strtoull(linebuf, &endp, 0); |
|
|
|
if (endp == linebuf || errno == ERANGE) |
|
return 0; |
|
|
|
return value; |
|
} |
|
|
|
/* read access to files which contain one string */ |
|
|
|
enum idlestate_string { |
|
IDLESTATE_DESC, |
|
IDLESTATE_NAME, |
|
MAX_IDLESTATE_STRING_FILES |
|
}; |
|
|
|
static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = { |
|
[IDLESTATE_DESC] = "desc", |
|
[IDLESTATE_NAME] = "name", |
|
}; |
|
|
|
|
|
static char *sysfs_idlestate_get_one_string(unsigned int cpu, |
|
unsigned int idlestate, |
|
enum idlestate_string which) |
|
{ |
|
char linebuf[MAX_LINE_LEN]; |
|
char *result; |
|
unsigned int len; |
|
|
|
if (which >= MAX_IDLESTATE_STRING_FILES) |
|
return NULL; |
|
|
|
len = sysfs_idlestate_read_file(cpu, idlestate, |
|
idlestate_string_files[which], |
|
linebuf, sizeof(linebuf)); |
|
if (len == 0) |
|
return NULL; |
|
|
|
result = strdup(linebuf); |
|
if (result == NULL) |
|
return NULL; |
|
|
|
if (result[strlen(result) - 1] == '\n') |
|
result[strlen(result) - 1] = '\0'; |
|
|
|
return result; |
|
} |
|
|
|
/* |
|
* Returns: |
|
* 1 if disabled |
|
* 0 if enabled |
|
* -1 if idlestate is not available |
|
* -2 if disabling is not supported by the kernel |
|
*/ |
|
int sysfs_is_idlestate_disabled(unsigned int cpu, |
|
unsigned int idlestate) |
|
{ |
|
if (sysfs_get_idlestate_count(cpu) <= idlestate) |
|
return -1; |
|
|
|
if (!sysfs_idlestate_file_exists(cpu, idlestate, |
|
idlestate_value_files[IDLESTATE_DISABLE])) |
|
return -2; |
|
return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_DISABLE); |
|
} |
|
|
|
/* |
|
* Pass 1 as last argument to disable or 0 to enable the state |
|
* Returns: |
|
* 0 on success |
|
* negative values on error, for example: |
|
* -1 if idlestate is not available |
|
* -2 if disabling is not supported by the kernel |
|
* -3 No write access to disable/enable C-states |
|
*/ |
|
int sysfs_idlestate_disable(unsigned int cpu, |
|
unsigned int idlestate, |
|
unsigned int disable) |
|
{ |
|
char value[SYSFS_PATH_MAX]; |
|
int bytes_written; |
|
|
|
if (sysfs_get_idlestate_count(cpu) <= idlestate) |
|
return -1; |
|
|
|
if (!sysfs_idlestate_file_exists(cpu, idlestate, |
|
idlestate_value_files[IDLESTATE_DISABLE])) |
|
return -2; |
|
|
|
snprintf(value, SYSFS_PATH_MAX, "%u", disable); |
|
|
|
bytes_written = sysfs_idlestate_write_file(cpu, idlestate, "disable", |
|
value, sizeof(disable)); |
|
if (bytes_written) |
|
return 0; |
|
return -3; |
|
} |
|
|
|
unsigned long sysfs_get_idlestate_latency(unsigned int cpu, |
|
unsigned int idlestate) |
|
{ |
|
return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_LATENCY); |
|
} |
|
|
|
unsigned long sysfs_get_idlestate_usage(unsigned int cpu, |
|
unsigned int idlestate) |
|
{ |
|
return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_USAGE); |
|
} |
|
|
|
unsigned long long sysfs_get_idlestate_time(unsigned int cpu, |
|
unsigned int idlestate) |
|
{ |
|
return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_TIME); |
|
} |
|
|
|
char *sysfs_get_idlestate_name(unsigned int cpu, unsigned int idlestate) |
|
{ |
|
return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_NAME); |
|
} |
|
|
|
char *sysfs_get_idlestate_desc(unsigned int cpu, unsigned int idlestate) |
|
{ |
|
return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_DESC); |
|
} |
|
|
|
/* |
|
* Returns number of supported C-states of CPU core cpu |
|
* Negativ in error case |
|
* Zero if cpuidle does not export any C-states |
|
*/ |
|
unsigned int sysfs_get_idlestate_count(unsigned int cpu) |
|
{ |
|
char file[SYSFS_PATH_MAX]; |
|
struct stat statbuf; |
|
int idlestates = 1; |
|
|
|
|
|
snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpuidle"); |
|
if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) |
|
return 0; |
|
|
|
snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpu%u/cpuidle/state0", cpu); |
|
if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) |
|
return 0; |
|
|
|
while (stat(file, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { |
|
snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU |
|
"cpu%u/cpuidle/state%d", cpu, idlestates); |
|
idlestates++; |
|
} |
|
idlestates--; |
|
return idlestates; |
|
} |
|
|
|
/* CPUidle general /sys/devices/system/cpu/cpuidle/ sysfs access ********/ |
|
|
|
/* |
|
* helper function to read file from /sys into given buffer |
|
* fname is a relative path under "cpu/cpuidle/" dir |
|
*/ |
|
static unsigned int sysfs_cpuidle_read_file(const char *fname, char *buf, |
|
size_t buflen) |
|
{ |
|
char path[SYSFS_PATH_MAX]; |
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpuidle/%s", fname); |
|
|
|
return sysfs_read_file(path, buf, buflen); |
|
} |
|
|
|
|
|
|
|
/* read access to files which contain one string */ |
|
|
|
enum cpuidle_string { |
|
CPUIDLE_GOVERNOR, |
|
CPUIDLE_GOVERNOR_RO, |
|
CPUIDLE_DRIVER, |
|
MAX_CPUIDLE_STRING_FILES |
|
}; |
|
|
|
static const char *cpuidle_string_files[MAX_CPUIDLE_STRING_FILES] = { |
|
[CPUIDLE_GOVERNOR] = "current_governor", |
|
[CPUIDLE_GOVERNOR_RO] = "current_governor_ro", |
|
[CPUIDLE_DRIVER] = "current_driver", |
|
}; |
|
|
|
|
|
static char *sysfs_cpuidle_get_one_string(enum cpuidle_string which) |
|
{ |
|
char linebuf[MAX_LINE_LEN]; |
|
char *result; |
|
unsigned int len; |
|
|
|
if (which >= MAX_CPUIDLE_STRING_FILES) |
|
return NULL; |
|
|
|
len = sysfs_cpuidle_read_file(cpuidle_string_files[which], |
|
linebuf, sizeof(linebuf)); |
|
if (len == 0) |
|
return NULL; |
|
|
|
result = strdup(linebuf); |
|
if (result == NULL) |
|
return NULL; |
|
|
|
if (result[strlen(result) - 1] == '\n') |
|
result[strlen(result) - 1] = '\0'; |
|
|
|
return result; |
|
} |
|
|
|
char *sysfs_get_cpuidle_governor(void) |
|
{ |
|
char *tmp = sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR_RO); |
|
if (!tmp) |
|
return sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR); |
|
else |
|
return tmp; |
|
} |
|
|
|
char *sysfs_get_cpuidle_driver(void) |
|
{ |
|
return sysfs_cpuidle_get_one_string(CPUIDLE_DRIVER); |
|
} |
|
/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */ |
|
|
|
/* |
|
* Get sched_mc or sched_smt settings |
|
* Pass "mc" or "smt" as argument |
|
* |
|
* Returns negative value on failure |
|
*/ |
|
int sysfs_get_sched(const char *smt_mc) |
|
{ |
|
return -ENODEV; |
|
} |
|
|
|
/* |
|
* Get sched_mc or sched_smt settings |
|
* Pass "mc" or "smt" as argument |
|
* |
|
* Returns negative value on failure |
|
*/ |
|
int sysfs_set_sched(const char *smt_mc, int val) |
|
{ |
|
return -ENODEV; |
|
}
|
|
|