// SPDX-License-Identifier: GPL-2.0-only /* * (C) 2004-2009 Dominik Brodowski * (C) 2011 Thomas Renninger Novell Inc. */ #include #include #include #include #include #include #include #include #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; }