diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /tools/power/cpupower/lib | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/power/cpupower/lib')
-rw-r--r-- | tools/power/cpupower/lib/cpufreq.c | 776 | ||||
-rw-r--r-- | tools/power/cpupower/lib/cpufreq.h | 210 | ||||
-rw-r--r-- | tools/power/cpupower/lib/cpuidle.c | 379 | ||||
-rw-r--r-- | tools/power/cpupower/lib/cpuidle.h | 24 | ||||
-rw-r--r-- | tools/power/cpupower/lib/cpupower.c | 191 | ||||
-rw-r--r-- | tools/power/cpupower/lib/cpupower.h | 36 | ||||
-rw-r--r-- | tools/power/cpupower/lib/cpupower_intern.h | 6 |
7 files changed, 1622 insertions, 0 deletions
diff --git a/tools/power/cpupower/lib/cpufreq.c b/tools/power/cpupower/lib/cpufreq.c new file mode 100644 index 000000000..c3b56db8b --- /dev/null +++ b/tools/power/cpupower/lib/cpufreq.c @@ -0,0 +1,776 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> + */ + + +#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 "cpufreq.h" +#include "cpupower_intern.h" + +/* CPUFREQ sysfs access **************************************************/ + +/* helper function to read file from /sys into given buffer */ +/* fname is a relative path under "cpuX/cpufreq" dir */ +static unsigned int sysfs_cpufreq_read_file(unsigned int cpu, const char *fname, + char *buf, size_t buflen) +{ + char path[SYSFS_PATH_MAX]; + + snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpufreq/%s", + cpu, fname); + return cpupower_read_sysfs(path, buf, buflen); +} + +/* helper function to write a new value to a /sys file */ +/* fname is a relative path under "cpuX/cpufreq" dir */ +static unsigned int sysfs_cpufreq_write_file(unsigned int cpu, + 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/cpufreq/%s", + cpu, 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 cpufreq_value { + CPUINFO_CUR_FREQ, + CPUINFO_MIN_FREQ, + CPUINFO_MAX_FREQ, + CPUINFO_LATENCY, + SCALING_CUR_FREQ, + SCALING_MIN_FREQ, + SCALING_MAX_FREQ, + STATS_NUM_TRANSITIONS, + MAX_CPUFREQ_VALUE_READ_FILES +}; + +static const char *cpufreq_value_files[MAX_CPUFREQ_VALUE_READ_FILES] = { + [CPUINFO_CUR_FREQ] = "cpuinfo_cur_freq", + [CPUINFO_MIN_FREQ] = "cpuinfo_min_freq", + [CPUINFO_MAX_FREQ] = "cpuinfo_max_freq", + [CPUINFO_LATENCY] = "cpuinfo_transition_latency", + [SCALING_CUR_FREQ] = "scaling_cur_freq", + [SCALING_MIN_FREQ] = "scaling_min_freq", + [SCALING_MAX_FREQ] = "scaling_max_freq", + [STATS_NUM_TRANSITIONS] = "stats/total_trans" +}; + + +static unsigned long sysfs_cpufreq_get_one_value(unsigned int cpu, + enum cpufreq_value which) +{ + unsigned long value; + unsigned int len; + char linebuf[MAX_LINE_LEN]; + char *endp; + + if (which >= MAX_CPUFREQ_VALUE_READ_FILES) + return 0; + + len = sysfs_cpufreq_read_file(cpu, cpufreq_value_files[which], + linebuf, sizeof(linebuf)); + + if (len == 0) + return 0; + + value = strtoul(linebuf, &endp, 0); + + if (endp == linebuf || errno == ERANGE) + return 0; + + return value; +} + +/* read access to files which contain one string */ + +enum cpufreq_string { + SCALING_DRIVER, + SCALING_GOVERNOR, + MAX_CPUFREQ_STRING_FILES +}; + +static const char *cpufreq_string_files[MAX_CPUFREQ_STRING_FILES] = { + [SCALING_DRIVER] = "scaling_driver", + [SCALING_GOVERNOR] = "scaling_governor", +}; + + +static char *sysfs_cpufreq_get_one_string(unsigned int cpu, + enum cpufreq_string which) +{ + char linebuf[MAX_LINE_LEN]; + char *result; + unsigned int len; + + if (which >= MAX_CPUFREQ_STRING_FILES) + return NULL; + + len = sysfs_cpufreq_read_file(cpu, cpufreq_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; +} + +/* write access */ + +enum cpufreq_write { + WRITE_SCALING_MIN_FREQ, + WRITE_SCALING_MAX_FREQ, + WRITE_SCALING_GOVERNOR, + WRITE_SCALING_SET_SPEED, + MAX_CPUFREQ_WRITE_FILES +}; + +static const char *cpufreq_write_files[MAX_CPUFREQ_WRITE_FILES] = { + [WRITE_SCALING_MIN_FREQ] = "scaling_min_freq", + [WRITE_SCALING_MAX_FREQ] = "scaling_max_freq", + [WRITE_SCALING_GOVERNOR] = "scaling_governor", + [WRITE_SCALING_SET_SPEED] = "scaling_setspeed", +}; + +static int sysfs_cpufreq_write_one_value(unsigned int cpu, + enum cpufreq_write which, + const char *new_value, size_t len) +{ + if (which >= MAX_CPUFREQ_WRITE_FILES) + return 0; + + if (sysfs_cpufreq_write_file(cpu, cpufreq_write_files[which], + new_value, len) != len) + return -ENODEV; + + return 0; +}; + +unsigned long cpufreq_get_freq_kernel(unsigned int cpu) +{ + return sysfs_cpufreq_get_one_value(cpu, SCALING_CUR_FREQ); +} + +unsigned long cpufreq_get_freq_hardware(unsigned int cpu) +{ + return sysfs_cpufreq_get_one_value(cpu, CPUINFO_CUR_FREQ); +} + +unsigned long cpufreq_get_transition_latency(unsigned int cpu) +{ + return sysfs_cpufreq_get_one_value(cpu, CPUINFO_LATENCY); +} + +int cpufreq_get_hardware_limits(unsigned int cpu, + unsigned long *min, + unsigned long *max) +{ + if ((!min) || (!max)) + return -EINVAL; + + *min = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MIN_FREQ); + if (!*min) + return -ENODEV; + + *max = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MAX_FREQ); + if (!*max) + return -ENODEV; + + return 0; +} + +char *cpufreq_get_driver(unsigned int cpu) +{ + return sysfs_cpufreq_get_one_string(cpu, SCALING_DRIVER); +} + +void cpufreq_put_driver(char *ptr) +{ + if (!ptr) + return; + free(ptr); +} + +struct cpufreq_policy *cpufreq_get_policy(unsigned int cpu) +{ + struct cpufreq_policy *policy; + + policy = malloc(sizeof(struct cpufreq_policy)); + if (!policy) + return NULL; + + policy->governor = sysfs_cpufreq_get_one_string(cpu, SCALING_GOVERNOR); + if (!policy->governor) { + free(policy); + return NULL; + } + policy->min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ); + policy->max = sysfs_cpufreq_get_one_value(cpu, SCALING_MAX_FREQ); + if ((!policy->min) || (!policy->max)) { + free(policy->governor); + free(policy); + return NULL; + } + + return policy; +} + +void cpufreq_put_policy(struct cpufreq_policy *policy) +{ + if ((!policy) || (!policy->governor)) + return; + + free(policy->governor); + policy->governor = NULL; + free(policy); +} + +struct cpufreq_available_governors *cpufreq_get_available_governors(unsigned + int cpu) +{ + struct cpufreq_available_governors *first = NULL; + struct cpufreq_available_governors *current = NULL; + char linebuf[MAX_LINE_LEN]; + unsigned int pos, i; + unsigned int len; + + len = sysfs_cpufreq_read_file(cpu, "scaling_available_governors", + linebuf, sizeof(linebuf)); + if (len == 0) + return NULL; + + pos = 0; + for (i = 0; i < len; i++) { + if (linebuf[i] == ' ' || linebuf[i] == '\n') { + if (i - pos < 2) + continue; + if (current) { + current->next = malloc(sizeof(*current)); + if (!current->next) + goto error_out; + current = current->next; + } else { + first = malloc(sizeof(*first)); + if (!first) + return NULL; + current = first; + } + current->first = first; + current->next = NULL; + + current->governor = malloc(i - pos + 1); + if (!current->governor) + goto error_out; + + memcpy(current->governor, linebuf + pos, i - pos); + current->governor[i - pos] = '\0'; + pos = i + 1; + } + } + + return first; + + error_out: + while (first) { + current = first->next; + if (first->governor) + free(first->governor); + free(first); + first = current; + } + return NULL; +} + +void cpufreq_put_available_governors(struct cpufreq_available_governors *any) +{ + struct cpufreq_available_governors *tmp, *next; + + if (!any) + return; + + tmp = any->first; + while (tmp) { + next = tmp->next; + if (tmp->governor) + free(tmp->governor); + free(tmp); + tmp = next; + } +} + + +struct cpufreq_available_frequencies +*cpufreq_get_available_frequencies(unsigned int cpu) +{ + struct cpufreq_available_frequencies *first = NULL; + struct cpufreq_available_frequencies *current = NULL; + char one_value[SYSFS_PATH_MAX]; + char linebuf[MAX_LINE_LEN]; + unsigned int pos, i; + unsigned int len; + + len = sysfs_cpufreq_read_file(cpu, "scaling_available_frequencies", + linebuf, sizeof(linebuf)); + if (len == 0) + return NULL; + + pos = 0; + for (i = 0; i < len; i++) { + if (linebuf[i] == ' ' || linebuf[i] == '\n') { + if (i - pos < 2) + continue; + if (i - pos >= SYSFS_PATH_MAX) + goto error_out; + if (current) { + current->next = malloc(sizeof(*current)); + if (!current->next) + goto error_out; + current = current->next; + } else { + first = malloc(sizeof(*first)); + if (!first) + return NULL; + current = first; + } + current->first = first; + current->next = NULL; + + memcpy(one_value, linebuf + pos, i - pos); + one_value[i - pos] = '\0'; + if (sscanf(one_value, "%lu", ¤t->frequency) != 1) + goto error_out; + + pos = i + 1; + } + } + + return first; + + error_out: + while (first) { + current = first->next; + free(first); + first = current; + } + return NULL; +} + +struct cpufreq_available_frequencies +*cpufreq_get_boost_frequencies(unsigned int cpu) +{ + struct cpufreq_available_frequencies *first = NULL; + struct cpufreq_available_frequencies *current = NULL; + char one_value[SYSFS_PATH_MAX]; + char linebuf[MAX_LINE_LEN]; + unsigned int pos, i; + unsigned int len; + + len = sysfs_cpufreq_read_file(cpu, "scaling_boost_frequencies", + linebuf, sizeof(linebuf)); + if (len == 0) + return NULL; + + pos = 0; + for (i = 0; i < len; i++) { + if (linebuf[i] == ' ' || linebuf[i] == '\n') { + if (i - pos < 2) + continue; + if (i - pos >= SYSFS_PATH_MAX) + goto error_out; + if (current) { + current->next = malloc(sizeof(*current)); + if (!current->next) + goto error_out; + current = current->next; + } else { + first = malloc(sizeof(*first)); + if (!first) + return NULL; + current = first; + } + current->first = first; + current->next = NULL; + + memcpy(one_value, linebuf + pos, i - pos); + one_value[i - pos] = '\0'; + if (sscanf(one_value, "%lu", ¤t->frequency) != 1) + goto error_out; + + pos = i + 1; + } + } + + return first; + + error_out: + while (first) { + current = first->next; + free(first); + first = current; + } + return NULL; +} + +void cpufreq_put_available_frequencies(struct cpufreq_available_frequencies *any) +{ + struct cpufreq_available_frequencies *tmp, *next; + + if (!any) + return; + + tmp = any->first; + while (tmp) { + next = tmp->next; + free(tmp); + tmp = next; + } +} + +void cpufreq_put_boost_frequencies(struct cpufreq_available_frequencies *any) +{ + cpufreq_put_available_frequencies(any); +} + +static struct cpufreq_affected_cpus *sysfs_get_cpu_list(unsigned int cpu, + const char *file) +{ + struct cpufreq_affected_cpus *first = NULL; + struct cpufreq_affected_cpus *current = NULL; + char one_value[SYSFS_PATH_MAX]; + char linebuf[MAX_LINE_LEN]; + unsigned int pos, i; + unsigned int len; + + len = sysfs_cpufreq_read_file(cpu, file, linebuf, sizeof(linebuf)); + if (len == 0) + return NULL; + + pos = 0; + for (i = 0; i < len; i++) { + if (i == len || linebuf[i] == ' ' || linebuf[i] == '\n') { + if (i - pos < 1) + continue; + if (i - pos >= SYSFS_PATH_MAX) + goto error_out; + if (current) { + current->next = malloc(sizeof(*current)); + if (!current->next) + goto error_out; + current = current->next; + } else { + first = malloc(sizeof(*first)); + if (!first) + return NULL; + current = first; + } + current->first = first; + current->next = NULL; + + memcpy(one_value, linebuf + pos, i - pos); + one_value[i - pos] = '\0'; + + if (sscanf(one_value, "%u", ¤t->cpu) != 1) + goto error_out; + + pos = i + 1; + } + } + + return first; + + error_out: + while (first) { + current = first->next; + free(first); + first = current; + } + return NULL; +} + +struct cpufreq_affected_cpus *cpufreq_get_affected_cpus(unsigned int cpu) +{ + return sysfs_get_cpu_list(cpu, "affected_cpus"); +} + +void cpufreq_put_affected_cpus(struct cpufreq_affected_cpus *any) +{ + struct cpufreq_affected_cpus *tmp, *next; + + if (!any) + return; + + tmp = any->first; + while (tmp) { + next = tmp->next; + free(tmp); + tmp = next; + } +} + + +struct cpufreq_affected_cpus *cpufreq_get_related_cpus(unsigned int cpu) +{ + return sysfs_get_cpu_list(cpu, "related_cpus"); +} + +void cpufreq_put_related_cpus(struct cpufreq_affected_cpus *any) +{ + cpufreq_put_affected_cpus(any); +} + +static int verify_gov(char *new_gov, char *passed_gov) +{ + unsigned int i, j = 0; + + if (!passed_gov || (strlen(passed_gov) > 19)) + return -EINVAL; + + strncpy(new_gov, passed_gov, 20); + for (i = 0; i < 20; i++) { + if (j) { + new_gov[i] = '\0'; + continue; + } + if ((new_gov[i] >= 'a') && (new_gov[i] <= 'z')) + continue; + + if ((new_gov[i] >= 'A') && (new_gov[i] <= 'Z')) + continue; + + if (new_gov[i] == '-') + continue; + + if (new_gov[i] == '_') + continue; + + if (new_gov[i] == '\0') { + j = 1; + continue; + } + return -EINVAL; + } + new_gov[19] = '\0'; + return 0; +} + +int cpufreq_set_policy(unsigned int cpu, struct cpufreq_policy *policy) +{ + char min[SYSFS_PATH_MAX]; + char max[SYSFS_PATH_MAX]; + char gov[SYSFS_PATH_MAX]; + int ret; + unsigned long old_min; + int write_max_first; + + if (!policy || !(policy->governor)) + return -EINVAL; + + if (policy->max < policy->min) + return -EINVAL; + + if (verify_gov(gov, policy->governor)) + return -EINVAL; + + snprintf(min, SYSFS_PATH_MAX, "%lu", policy->min); + snprintf(max, SYSFS_PATH_MAX, "%lu", policy->max); + + old_min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ); + write_max_first = (old_min && (policy->max < old_min) ? 0 : 1); + + if (write_max_first) { + ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ, + max, strlen(max)); + if (ret) + return ret; + } + + ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ, min, + strlen(min)); + if (ret) + return ret; + + if (!write_max_first) { + ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ, + max, strlen(max)); + if (ret) + return ret; + } + + return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR, + gov, strlen(gov)); +} + + +int cpufreq_modify_policy_min(unsigned int cpu, unsigned long min_freq) +{ + char value[SYSFS_PATH_MAX]; + + snprintf(value, SYSFS_PATH_MAX, "%lu", min_freq); + + return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ, + value, strlen(value)); +} + + +int cpufreq_modify_policy_max(unsigned int cpu, unsigned long max_freq) +{ + char value[SYSFS_PATH_MAX]; + + snprintf(value, SYSFS_PATH_MAX, "%lu", max_freq); + + return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ, + value, strlen(value)); +} + +int cpufreq_modify_policy_governor(unsigned int cpu, char *governor) +{ + char new_gov[SYSFS_PATH_MAX]; + + if ((!governor) || (strlen(governor) > 19)) + return -EINVAL; + + if (verify_gov(new_gov, governor)) + return -EINVAL; + + return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR, + new_gov, strlen(new_gov)); +} + +int cpufreq_set_frequency(unsigned int cpu, unsigned long target_frequency) +{ + struct cpufreq_policy *pol = cpufreq_get_policy(cpu); + char userspace_gov[] = "userspace"; + char freq[SYSFS_PATH_MAX]; + int ret; + + if (!pol) + return -ENODEV; + + if (strncmp(pol->governor, userspace_gov, 9) != 0) { + ret = cpufreq_modify_policy_governor(cpu, userspace_gov); + if (ret) { + cpufreq_put_policy(pol); + return ret; + } + } + + cpufreq_put_policy(pol); + + snprintf(freq, SYSFS_PATH_MAX, "%lu", target_frequency); + + return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_SET_SPEED, + freq, strlen(freq)); +} + +struct cpufreq_stats *cpufreq_get_stats(unsigned int cpu, + unsigned long long *total_time) +{ + struct cpufreq_stats *first = NULL; + struct cpufreq_stats *current = NULL; + char one_value[SYSFS_PATH_MAX]; + char linebuf[MAX_LINE_LEN]; + unsigned int pos, i; + unsigned int len; + + len = sysfs_cpufreq_read_file(cpu, "stats/time_in_state", + linebuf, sizeof(linebuf)); + if (len == 0) + return NULL; + + *total_time = 0; + pos = 0; + for (i = 0; i < len; i++) { + if (i == strlen(linebuf) || linebuf[i] == '\n') { + if (i - pos < 2) + continue; + if ((i - pos) >= SYSFS_PATH_MAX) + goto error_out; + if (current) { + current->next = malloc(sizeof(*current)); + if (!current->next) + goto error_out; + current = current->next; + } else { + first = malloc(sizeof(*first)); + if (!first) + return NULL; + current = first; + } + current->first = first; + current->next = NULL; + + memcpy(one_value, linebuf + pos, i - pos); + one_value[i - pos] = '\0'; + if (sscanf(one_value, "%lu %llu", + ¤t->frequency, + ¤t->time_in_state) != 2) + goto error_out; + + *total_time = *total_time + current->time_in_state; + pos = i + 1; + } + } + + return first; + + error_out: + while (first) { + current = first->next; + free(first); + first = current; + } + return NULL; +} + +void cpufreq_put_stats(struct cpufreq_stats *any) +{ + struct cpufreq_stats *tmp, *next; + + if (!any) + return; + + tmp = any->first; + while (tmp) { + next = tmp->next; + free(tmp); + tmp = next; + } +} + +unsigned long cpufreq_get_transitions(unsigned int cpu) +{ + return sysfs_cpufreq_get_one_value(cpu, STATS_NUM_TRANSITIONS); +} diff --git a/tools/power/cpupower/lib/cpufreq.h b/tools/power/cpupower/lib/cpufreq.h new file mode 100644 index 000000000..95f4fd9e2 --- /dev/null +++ b/tools/power/cpupower/lib/cpufreq.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * cpufreq.h - definitions for libcpufreq + * + * Copyright (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> + */ + +#ifndef __CPUPOWER_CPUFREQ_H__ +#define __CPUPOWER_CPUFREQ_H__ + +struct cpufreq_policy { + unsigned long min; + unsigned long max; + char *governor; +}; + +struct cpufreq_available_governors { + char *governor; + struct cpufreq_available_governors *next; + struct cpufreq_available_governors *first; +}; + +struct cpufreq_available_frequencies { + unsigned long frequency; + struct cpufreq_available_frequencies *next; + struct cpufreq_available_frequencies *first; +}; + + +struct cpufreq_affected_cpus { + unsigned int cpu; + struct cpufreq_affected_cpus *next; + struct cpufreq_affected_cpus *first; +}; + +struct cpufreq_stats { + unsigned long frequency; + unsigned long long time_in_state; + struct cpufreq_stats *next; + struct cpufreq_stats *first; +}; + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* determine current CPU frequency + * - _kernel variant means kernel's opinion of CPU frequency + * - _hardware variant means actual hardware CPU frequency, + * which is only available to root. + * + * returns 0 on failure, else frequency in kHz. + */ + +unsigned long cpufreq_get_freq_kernel(unsigned int cpu); + +unsigned long cpufreq_get_freq_hardware(unsigned int cpu); + +#define cpufreq_get(cpu) cpufreq_get_freq_kernel(cpu); + + +/* determine CPU transition latency + * + * returns 0 on failure, else transition latency in 10^(-9) s = nanoseconds + */ +unsigned long cpufreq_get_transition_latency(unsigned int cpu); + + +/* determine hardware CPU frequency limits + * + * These may be limited further by thermal, energy or other + * considerations by cpufreq policy notifiers in the kernel. + */ + +int cpufreq_get_hardware_limits(unsigned int cpu, + unsigned long *min, + unsigned long *max); + + +/* determine CPUfreq driver used + * + * Remember to call cpufreq_put_driver when no longer needed + * to avoid memory leakage, please. + */ + +char *cpufreq_get_driver(unsigned int cpu); + +void cpufreq_put_driver(char *ptr); + + +/* determine CPUfreq policy currently used + * + * Remember to call cpufreq_put_policy when no longer needed + * to avoid memory leakage, please. + */ + + +struct cpufreq_policy *cpufreq_get_policy(unsigned int cpu); + +void cpufreq_put_policy(struct cpufreq_policy *policy); + + +/* determine CPUfreq governors currently available + * + * may be modified by modprobe'ing or rmmod'ing other governors. Please + * free allocated memory by calling cpufreq_put_available_governors + * after use. + */ + + +struct cpufreq_available_governors +*cpufreq_get_available_governors(unsigned int cpu); + +void cpufreq_put_available_governors( + struct cpufreq_available_governors *first); + + +/* determine CPU frequency states available + * + * Only present on _some_ ->target() cpufreq drivers. For information purposes + * only. Please free allocated memory by calling + * cpufreq_put_frequencies after use. + */ + +struct cpufreq_available_frequencies +*cpufreq_get_available_frequencies(unsigned int cpu); + +void cpufreq_put_available_frequencies( + struct cpufreq_available_frequencies *first); + +struct cpufreq_available_frequencies +*cpufreq_get_boost_frequencies(unsigned int cpu); + +void cpufreq_put_boost_frequencies( + struct cpufreq_available_frequencies *first); + + +/* determine affected CPUs + * + * Remember to call cpufreq_put_affected_cpus when no longer needed + * to avoid memory leakage, please. + */ + +struct cpufreq_affected_cpus *cpufreq_get_affected_cpus(unsigned + int cpu); + +void cpufreq_put_affected_cpus(struct cpufreq_affected_cpus *first); + + +/* determine related CPUs + * + * Remember to call cpufreq_put_related_cpus when no longer needed + * to avoid memory leakage, please. + */ + +struct cpufreq_affected_cpus *cpufreq_get_related_cpus(unsigned + int cpu); + +void cpufreq_put_related_cpus(struct cpufreq_affected_cpus *first); + + +/* determine stats for cpufreq subsystem + * + * This is not available in all kernel versions or configurations. + */ + +struct cpufreq_stats *cpufreq_get_stats(unsigned int cpu, + unsigned long long *total_time); + +void cpufreq_put_stats(struct cpufreq_stats *stats); + +unsigned long cpufreq_get_transitions(unsigned int cpu); + + +/* set new cpufreq policy + * + * Tries to set the passed policy as new policy as close as possible, + * but results may differ depending e.g. on governors being available. + */ + +int cpufreq_set_policy(unsigned int cpu, struct cpufreq_policy *policy); + + +/* modify a policy by only changing min/max freq or governor + * + * Does not check whether result is what was intended. + */ + +int cpufreq_modify_policy_min(unsigned int cpu, unsigned long min_freq); +int cpufreq_modify_policy_max(unsigned int cpu, unsigned long max_freq); +int cpufreq_modify_policy_governor(unsigned int cpu, char *governor); + + +/* set a specific frequency + * + * Does only work if userspace governor can be used and no external + * interference (other calls to this function or to set/modify_policy) + * occurs. Also does not work on ->range() cpufreq drivers. + */ + +int cpufreq_set_frequency(unsigned int cpu, + unsigned long target_frequency); + +#ifdef __cplusplus +} +#endif + +#endif /* _CPUFREQ_H */ diff --git a/tools/power/cpupower/lib/cpuidle.c b/tools/power/cpupower/lib/cpuidle.c new file mode 100644 index 000000000..479c5971a --- /dev/null +++ b/tools/power/cpupower/lib/cpuidle.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> + * (C) 2011 Thomas Renninger <trenn@novell.com> 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 "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 cpupower_read_sysfs(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 */ diff --git a/tools/power/cpupower/lib/cpuidle.h b/tools/power/cpupower/lib/cpuidle.h new file mode 100644 index 000000000..2e10fead2 --- /dev/null +++ b/tools/power/cpupower/lib/cpuidle.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __CPUPOWER_CPUIDLE_H__ +#define __CPUPOWER_CPUIDLE_H__ + +int cpuidle_is_state_disabled(unsigned int cpu, + unsigned int idlestate); +int cpuidle_state_disable(unsigned int cpu, unsigned int idlestate, + unsigned int disable); +unsigned long cpuidle_state_latency(unsigned int cpu, + unsigned int idlestate); +unsigned long cpuidle_state_usage(unsigned int cpu, + unsigned int idlestate); +unsigned long long cpuidle_state_time(unsigned int cpu, + unsigned int idlestate); +char *cpuidle_state_name(unsigned int cpu, + unsigned int idlestate); +char *cpuidle_state_desc(unsigned int cpu, + unsigned int idlestate); +unsigned int cpuidle_state_count(unsigned int cpu); + +char *cpuidle_get_governor(void); +char *cpuidle_get_driver(void); + +#endif /* __CPUPOWER_HELPERS_SYSFS_H__ */ diff --git a/tools/power/cpupower/lib/cpupower.c b/tools/power/cpupower/lib/cpupower.c new file mode 100644 index 000000000..3656e6975 --- /dev/null +++ b/tools/power/cpupower/lib/cpupower.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> + +#include "cpupower.h" +#include "cpupower_intern.h" + +unsigned int cpupower_read_sysfs(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 cpupower_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; +} + +/* returns -1 on failure, 0 on success */ +static int sysfs_topology_read_file(unsigned int cpu, const char *fname, int *result) +{ + char linebuf[MAX_LINE_LEN]; + char *endp; + char path[SYSFS_PATH_MAX]; + + snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/topology/%s", + cpu, fname); + if (cpupower_read_sysfs(path, linebuf, MAX_LINE_LEN) == 0) + return -1; + *result = strtol(linebuf, &endp, 0); + if (endp == linebuf || errno == ERANGE) + return -1; + return 0; +} + +static int __compare(const void *t1, const void *t2) +{ + struct cpuid_core_info *top1 = (struct cpuid_core_info *)t1; + struct cpuid_core_info *top2 = (struct cpuid_core_info *)t2; + if (top1->pkg < top2->pkg) + return -1; + else if (top1->pkg > top2->pkg) + return 1; + else if (top1->core < top2->core) + return -1; + else if (top1->core > top2->core) + return 1; + else if (top1->cpu < top2->cpu) + return -1; + else if (top1->cpu > top2->cpu) + return 1; + else + return 0; +} + +/* + * Returns amount of cpus, negative on error, cpu_top must be + * passed to cpu_topology_release to free resources + * + * Array is sorted after ->pkg, ->core, then ->cpu + */ +int get_cpu_topology(struct cpupower_topology *cpu_top) +{ + int cpu, last_pkg, cpus = sysconf(_SC_NPROCESSORS_CONF); + + cpu_top->core_info = malloc(sizeof(struct cpuid_core_info) * cpus); + if (cpu_top->core_info == NULL) + return -ENOMEM; + cpu_top->pkgs = cpu_top->cores = 0; + for (cpu = 0; cpu < cpus; cpu++) { + cpu_top->core_info[cpu].cpu = cpu; + cpu_top->core_info[cpu].is_online = cpupower_is_cpu_online(cpu); + if(sysfs_topology_read_file( + cpu, + "physical_package_id", + &(cpu_top->core_info[cpu].pkg)) < 0) { + cpu_top->core_info[cpu].pkg = -1; + cpu_top->core_info[cpu].core = -1; + continue; + } + if(sysfs_topology_read_file( + cpu, + "core_id", + &(cpu_top->core_info[cpu].core)) < 0) { + cpu_top->core_info[cpu].pkg = -1; + cpu_top->core_info[cpu].core = -1; + continue; + } + } + + qsort(cpu_top->core_info, cpus, sizeof(struct cpuid_core_info), + __compare); + + /* Count the number of distinct pkgs values. This works + because the primary sort of the core_info struct was just + done by pkg value. */ + last_pkg = cpu_top->core_info[0].pkg; + for(cpu = 1; cpu < cpus; cpu++) { + if (cpu_top->core_info[cpu].pkg != last_pkg && + cpu_top->core_info[cpu].pkg != -1) { + + last_pkg = cpu_top->core_info[cpu].pkg; + cpu_top->pkgs++; + } + } + if (!(cpu_top->core_info[0].pkg == -1)) + cpu_top->pkgs++; + + /* Intel's cores count is not consecutively numbered, there may + * be a core_id of 3, but none of 2. Assume there always is 0 + * Get amount of cores by counting duplicates in a package + for (cpu = 0; cpu_top->core_info[cpu].pkg = 0 && cpu < cpus; cpu++) { + if (cpu_top->core_info[cpu].core == 0) + cpu_top->cores++; + */ + return cpus; +} + +void cpu_topology_release(struct cpupower_topology cpu_top) +{ + free(cpu_top.core_info); +} diff --git a/tools/power/cpupower/lib/cpupower.h b/tools/power/cpupower/lib/cpupower.h new file mode 100644 index 000000000..e4e4292ea --- /dev/null +++ b/tools/power/cpupower/lib/cpupower.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __CPUPOWER_CPUPOWER_H__ +#define __CPUPOWER_CPUPOWER_H__ + +struct cpupower_topology { + /* Amount of CPU cores, packages and threads per core in the system */ + unsigned int cores; + unsigned int pkgs; + unsigned int threads; /* per core */ + + /* Array gets mallocated with cores entries, holding per core info */ + struct cpuid_core_info *core_info; +}; + +struct cpuid_core_info { + int pkg; + int core; + int cpu; + + /* flags */ + unsigned int is_online:1; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +int get_cpu_topology(struct cpupower_topology *cpu_top); +void cpu_topology_release(struct cpupower_topology cpu_top); +int cpupower_is_cpu_online(unsigned int cpu); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/power/cpupower/lib/cpupower_intern.h b/tools/power/cpupower/lib/cpupower_intern.h new file mode 100644 index 000000000..4887c76d2 --- /dev/null +++ b/tools/power/cpupower/lib/cpupower_intern.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define PATH_TO_CPU "/sys/devices/system/cpu/" +#define MAX_LINE_LEN 4096 +#define SYSFS_PATH_MAX 255 + +unsigned int cpupower_read_sysfs(const char *path, char *buf, size_t buflen); |