| /* | |
| * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> | |
| * (C) 2011 Thomas Renninger <trenn@novell.com> Novell Inc. | |
| * | |
| * Licensed under the terms of the GNU GPL License version 2. | |
| */ | |
| #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 "cpuidle.h" | |
| #include "cpupower_intern.h" | |
| /* | |
| * 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. | |
| */ | |
| static | |
| unsigned int cpuidle_state_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 | |
| */ | |
| static | |
| unsigned int cpuidle_state_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 cpuidle_state_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 cpuidle_state_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 = cpuidle_state_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 *cpuidle_state_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 = cpuidle_state_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 cpuidle_is_state_disabled(unsigned int cpu, | |
| unsigned int idlestate) | |
| { | |
| if (cpuidle_state_count(cpu) <= idlestate) | |
| return -1; | |
| if (!cpuidle_state_file_exists(cpu, idlestate, | |
| idlestate_value_files[IDLESTATE_DISABLE])) | |
| return -2; | |
| return cpuidle_state_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 cpuidle_state_disable(unsigned int cpu, | |
| unsigned int idlestate, | |
| unsigned int disable) | |
| { | |
| char value[SYSFS_PATH_MAX]; | |
| int bytes_written; | |
| if (cpuidle_state_count(cpu) <= idlestate) | |
| return -1; | |
| if (!cpuidle_state_file_exists(cpu, idlestate, | |
| idlestate_value_files[IDLESTATE_DISABLE])) | |
| return -2; | |
| snprintf(value, SYSFS_PATH_MAX, "%u", disable); | |
| bytes_written = cpuidle_state_write_file(cpu, idlestate, "disable", | |
| value, sizeof(disable)); | |
| if (bytes_written) | |
| return 0; | |
| return -3; | |
| } | |
| unsigned long cpuidle_state_latency(unsigned int cpu, | |
| unsigned int idlestate) | |
| { | |
| return cpuidle_state_get_one_value(cpu, idlestate, IDLESTATE_LATENCY); | |
| } | |
| unsigned long cpuidle_state_usage(unsigned int cpu, | |
| unsigned int idlestate) | |
| { | |
| return cpuidle_state_get_one_value(cpu, idlestate, IDLESTATE_USAGE); | |
| } | |
| unsigned long long cpuidle_state_time(unsigned int cpu, | |
| unsigned int idlestate) | |
| { | |
| return cpuidle_state_get_one_value(cpu, idlestate, IDLESTATE_TIME); | |
| } | |
| char *cpuidle_state_name(unsigned int cpu, unsigned int idlestate) | |
| { | |
| return cpuidle_state_get_one_string(cpu, idlestate, IDLESTATE_NAME); | |
| } | |
| char *cpuidle_state_desc(unsigned int cpu, unsigned int idlestate) | |
| { | |
| return cpuidle_state_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 cpuidle_state_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 *cpuidle_get_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 *cpuidle_get_driver(void) | |
| { | |
| return sysfs_cpuidle_get_one_string(CPUIDLE_DRIVER); | |
| } | |
| /* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */ |