786 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>4 */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 "cpufreq.h"17#include "cpupower_intern.h"18 19/* CPUFREQ sysfs access **************************************************/20 21/* helper function to read file from /sys into given buffer */22/* fname is a relative path under "cpuX/cpufreq" dir */23static unsigned int sysfs_cpufreq_read_file(unsigned int cpu, const char *fname,24 char *buf, size_t buflen)25{26 char path[SYSFS_PATH_MAX];27 28 snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpufreq/%s",29 cpu, fname);30 return cpupower_read_sysfs(path, buf, buflen);31}32 33/* helper function to write a new value to a /sys file */34/* fname is a relative path under "cpuX/cpufreq" dir */35static unsigned int sysfs_cpufreq_write_file(unsigned int cpu,36 const char *fname,37 const char *value, size_t len)38{39 char path[SYSFS_PATH_MAX];40 int fd;41 ssize_t numwrite;42 43 snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpufreq/%s",44 cpu, fname);45 46 fd = open(path, O_WRONLY);47 if (fd == -1)48 return 0;49 50 numwrite = write(fd, value, len);51 if (numwrite < 1) {52 close(fd);53 return 0;54 }55 56 close(fd);57 58 return (unsigned int) numwrite;59}60 61/* read access to files which contain one numeric value */62 63enum cpufreq_value {64 CPUINFO_CUR_FREQ,65 CPUINFO_MIN_FREQ,66 CPUINFO_MAX_FREQ,67 CPUINFO_LATENCY,68 SCALING_CUR_FREQ,69 SCALING_MIN_FREQ,70 SCALING_MAX_FREQ,71 STATS_NUM_TRANSITIONS,72 MAX_CPUFREQ_VALUE_READ_FILES73};74 75static const char *cpufreq_value_files[MAX_CPUFREQ_VALUE_READ_FILES] = {76 [CPUINFO_CUR_FREQ] = "cpuinfo_cur_freq",77 [CPUINFO_MIN_FREQ] = "cpuinfo_min_freq",78 [CPUINFO_MAX_FREQ] = "cpuinfo_max_freq",79 [CPUINFO_LATENCY] = "cpuinfo_transition_latency",80 [SCALING_CUR_FREQ] = "scaling_cur_freq",81 [SCALING_MIN_FREQ] = "scaling_min_freq",82 [SCALING_MAX_FREQ] = "scaling_max_freq",83 [STATS_NUM_TRANSITIONS] = "stats/total_trans"84};85 86unsigned long cpufreq_get_sysfs_value_from_table(unsigned int cpu,87 const char **table,88 unsigned int index,89 unsigned int size)90{91 unsigned long value;92 unsigned int len;93 char linebuf[MAX_LINE_LEN];94 char *endp;95 96 if (!table || index >= size || !table[index])97 return 0;98 99 len = sysfs_cpufreq_read_file(cpu, table[index], linebuf,100 sizeof(linebuf));101 102 if (len == 0)103 return 0;104 105 value = strtoul(linebuf, &endp, 0);106 107 if (endp == linebuf || errno == ERANGE)108 return 0;109 110 return value;111}112 113static unsigned long sysfs_cpufreq_get_one_value(unsigned int cpu,114 enum cpufreq_value which)115{116 return cpufreq_get_sysfs_value_from_table(cpu, cpufreq_value_files,117 which,118 MAX_CPUFREQ_VALUE_READ_FILES);119}120 121/* read access to files which contain one string */122 123enum cpufreq_string {124 SCALING_DRIVER,125 SCALING_GOVERNOR,126 MAX_CPUFREQ_STRING_FILES127};128 129static const char *cpufreq_string_files[MAX_CPUFREQ_STRING_FILES] = {130 [SCALING_DRIVER] = "scaling_driver",131 [SCALING_GOVERNOR] = "scaling_governor",132};133 134 135static char *sysfs_cpufreq_get_one_string(unsigned int cpu,136 enum cpufreq_string which)137{138 char linebuf[MAX_LINE_LEN];139 char *result;140 unsigned int len;141 142 if (which >= MAX_CPUFREQ_STRING_FILES)143 return NULL;144 145 len = sysfs_cpufreq_read_file(cpu, cpufreq_string_files[which],146 linebuf, sizeof(linebuf));147 if (len == 0)148 return NULL;149 150 result = strdup(linebuf);151 if (result == NULL)152 return NULL;153 154 if (result[strlen(result) - 1] == '\n')155 result[strlen(result) - 1] = '\0';156 157 return result;158}159 160/* write access */161 162enum cpufreq_write {163 WRITE_SCALING_MIN_FREQ,164 WRITE_SCALING_MAX_FREQ,165 WRITE_SCALING_GOVERNOR,166 WRITE_SCALING_SET_SPEED,167 MAX_CPUFREQ_WRITE_FILES168};169 170static const char *cpufreq_write_files[MAX_CPUFREQ_WRITE_FILES] = {171 [WRITE_SCALING_MIN_FREQ] = "scaling_min_freq",172 [WRITE_SCALING_MAX_FREQ] = "scaling_max_freq",173 [WRITE_SCALING_GOVERNOR] = "scaling_governor",174 [WRITE_SCALING_SET_SPEED] = "scaling_setspeed",175};176 177static int sysfs_cpufreq_write_one_value(unsigned int cpu,178 enum cpufreq_write which,179 const char *new_value, size_t len)180{181 if (which >= MAX_CPUFREQ_WRITE_FILES)182 return 0;183 184 if (sysfs_cpufreq_write_file(cpu, cpufreq_write_files[which],185 new_value, len) != len)186 return -ENODEV;187 188 return 0;189};190 191unsigned long cpufreq_get_freq_kernel(unsigned int cpu)192{193 return sysfs_cpufreq_get_one_value(cpu, SCALING_CUR_FREQ);194}195 196unsigned long cpufreq_get_freq_hardware(unsigned int cpu)197{198 return sysfs_cpufreq_get_one_value(cpu, CPUINFO_CUR_FREQ);199}200 201unsigned long cpufreq_get_transition_latency(unsigned int cpu)202{203 return sysfs_cpufreq_get_one_value(cpu, CPUINFO_LATENCY);204}205 206int cpufreq_get_hardware_limits(unsigned int cpu,207 unsigned long *min,208 unsigned long *max)209{210 if ((!min) || (!max))211 return -EINVAL;212 213 *min = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MIN_FREQ);214 if (!*min)215 return -ENODEV;216 217 *max = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MAX_FREQ);218 if (!*max)219 return -ENODEV;220 221 return 0;222}223 224char *cpufreq_get_driver(unsigned int cpu)225{226 return sysfs_cpufreq_get_one_string(cpu, SCALING_DRIVER);227}228 229void cpufreq_put_driver(char *ptr)230{231 if (!ptr)232 return;233 free(ptr);234}235 236struct cpufreq_policy *cpufreq_get_policy(unsigned int cpu)237{238 struct cpufreq_policy *policy;239 240 policy = malloc(sizeof(struct cpufreq_policy));241 if (!policy)242 return NULL;243 244 policy->governor = sysfs_cpufreq_get_one_string(cpu, SCALING_GOVERNOR);245 if (!policy->governor) {246 free(policy);247 return NULL;248 }249 policy->min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ);250 policy->max = sysfs_cpufreq_get_one_value(cpu, SCALING_MAX_FREQ);251 if ((!policy->min) || (!policy->max)) {252 free(policy->governor);253 free(policy);254 return NULL;255 }256 257 return policy;258}259 260void cpufreq_put_policy(struct cpufreq_policy *policy)261{262 if ((!policy) || (!policy->governor))263 return;264 265 free(policy->governor);266 policy->governor = NULL;267 free(policy);268}269 270struct cpufreq_available_governors *cpufreq_get_available_governors(unsigned271 int cpu)272{273 struct cpufreq_available_governors *first = NULL;274 struct cpufreq_available_governors *current = NULL;275 char linebuf[MAX_LINE_LEN];276 unsigned int pos, i;277 unsigned int len;278 279 len = sysfs_cpufreq_read_file(cpu, "scaling_available_governors",280 linebuf, sizeof(linebuf));281 if (len == 0)282 return NULL;283 284 pos = 0;285 for (i = 0; i < len; i++) {286 if (linebuf[i] == ' ' || linebuf[i] == '\n') {287 if (i - pos < 2)288 continue;289 if (current) {290 current->next = malloc(sizeof(*current));291 if (!current->next)292 goto error_out;293 current = current->next;294 } else {295 first = malloc(sizeof(*first));296 if (!first)297 return NULL;298 current = first;299 }300 current->first = first;301 current->next = NULL;302 303 current->governor = malloc(i - pos + 1);304 if (!current->governor)305 goto error_out;306 307 memcpy(current->governor, linebuf + pos, i - pos);308 current->governor[i - pos] = '\0';309 pos = i + 1;310 }311 }312 313 return first;314 315 error_out:316 while (first) {317 current = first->next;318 if (first->governor)319 free(first->governor);320 free(first);321 first = current;322 }323 return NULL;324}325 326void cpufreq_put_available_governors(struct cpufreq_available_governors *any)327{328 struct cpufreq_available_governors *tmp, *next;329 330 if (!any)331 return;332 333 tmp = any->first;334 while (tmp) {335 next = tmp->next;336 if (tmp->governor)337 free(tmp->governor);338 free(tmp);339 tmp = next;340 }341}342 343 344struct cpufreq_available_frequencies345*cpufreq_get_available_frequencies(unsigned int cpu)346{347 struct cpufreq_available_frequencies *first = NULL;348 struct cpufreq_available_frequencies *current = NULL;349 char one_value[SYSFS_PATH_MAX];350 char linebuf[MAX_LINE_LEN];351 unsigned int pos, i;352 unsigned int len;353 354 len = sysfs_cpufreq_read_file(cpu, "scaling_available_frequencies",355 linebuf, sizeof(linebuf));356 if (len == 0)357 return NULL;358 359 pos = 0;360 for (i = 0; i < len; i++) {361 if (linebuf[i] == ' ' || linebuf[i] == '\n') {362 if (i - pos < 2)363 continue;364 if (i - pos >= SYSFS_PATH_MAX)365 goto error_out;366 if (current) {367 current->next = malloc(sizeof(*current));368 if (!current->next)369 goto error_out;370 current = current->next;371 } else {372 first = malloc(sizeof(*first));373 if (!first)374 return NULL;375 current = first;376 }377 current->first = first;378 current->next = NULL;379 380 memcpy(one_value, linebuf + pos, i - pos);381 one_value[i - pos] = '\0';382 if (sscanf(one_value, "%lu", ¤t->frequency) != 1)383 goto error_out;384 385 pos = i + 1;386 }387 }388 389 return first;390 391 error_out:392 while (first) {393 current = first->next;394 free(first);395 first = current;396 }397 return NULL;398}399 400struct cpufreq_available_frequencies401*cpufreq_get_boost_frequencies(unsigned int cpu)402{403 struct cpufreq_available_frequencies *first = NULL;404 struct cpufreq_available_frequencies *current = NULL;405 char one_value[SYSFS_PATH_MAX];406 char linebuf[MAX_LINE_LEN];407 unsigned int pos, i;408 unsigned int len;409 410 len = sysfs_cpufreq_read_file(cpu, "scaling_boost_frequencies",411 linebuf, sizeof(linebuf));412 if (len == 0)413 return NULL;414 415 pos = 0;416 for (i = 0; i < len; i++) {417 if (linebuf[i] == ' ' || linebuf[i] == '\n') {418 if (i - pos < 2)419 continue;420 if (i - pos >= SYSFS_PATH_MAX)421 goto error_out;422 if (current) {423 current->next = malloc(sizeof(*current));424 if (!current->next)425 goto error_out;426 current = current->next;427 } else {428 first = malloc(sizeof(*first));429 if (!first)430 return NULL;431 current = first;432 }433 current->first = first;434 current->next = NULL;435 436 memcpy(one_value, linebuf + pos, i - pos);437 one_value[i - pos] = '\0';438 if (sscanf(one_value, "%lu", ¤t->frequency) != 1)439 goto error_out;440 441 pos = i + 1;442 }443 }444 445 return first;446 447 error_out:448 while (first) {449 current = first->next;450 free(first);451 first = current;452 }453 return NULL;454}455 456void cpufreq_put_available_frequencies(struct cpufreq_available_frequencies *any)457{458 struct cpufreq_available_frequencies *tmp, *next;459 460 if (!any)461 return;462 463 tmp = any->first;464 while (tmp) {465 next = tmp->next;466 free(tmp);467 tmp = next;468 }469}470 471void cpufreq_put_boost_frequencies(struct cpufreq_available_frequencies *any)472{473 cpufreq_put_available_frequencies(any);474}475 476static struct cpufreq_affected_cpus *sysfs_get_cpu_list(unsigned int cpu,477 const char *file)478{479 struct cpufreq_affected_cpus *first = NULL;480 struct cpufreq_affected_cpus *current = NULL;481 char one_value[SYSFS_PATH_MAX];482 char linebuf[MAX_LINE_LEN];483 unsigned int pos, i;484 unsigned int len;485 486 len = sysfs_cpufreq_read_file(cpu, file, linebuf, sizeof(linebuf));487 if (len == 0)488 return NULL;489 490 pos = 0;491 for (i = 0; i < len; i++) {492 if (i == len || linebuf[i] == ' ' || linebuf[i] == '\n') {493 if (i - pos < 1)494 continue;495 if (i - pos >= SYSFS_PATH_MAX)496 goto error_out;497 if (current) {498 current->next = malloc(sizeof(*current));499 if (!current->next)500 goto error_out;501 current = current->next;502 } else {503 first = malloc(sizeof(*first));504 if (!first)505 return NULL;506 current = first;507 }508 current->first = first;509 current->next = NULL;510 511 memcpy(one_value, linebuf + pos, i - pos);512 one_value[i - pos] = '\0';513 514 if (sscanf(one_value, "%u", ¤t->cpu) != 1)515 goto error_out;516 517 pos = i + 1;518 }519 }520 521 return first;522 523 error_out:524 while (first) {525 current = first->next;526 free(first);527 first = current;528 }529 return NULL;530}531 532struct cpufreq_affected_cpus *cpufreq_get_affected_cpus(unsigned int cpu)533{534 return sysfs_get_cpu_list(cpu, "affected_cpus");535}536 537void cpufreq_put_affected_cpus(struct cpufreq_affected_cpus *any)538{539 struct cpufreq_affected_cpus *tmp, *next;540 541 if (!any)542 return;543 544 tmp = any->first;545 while (tmp) {546 next = tmp->next;547 free(tmp);548 tmp = next;549 }550}551 552 553struct cpufreq_affected_cpus *cpufreq_get_related_cpus(unsigned int cpu)554{555 return sysfs_get_cpu_list(cpu, "related_cpus");556}557 558void cpufreq_put_related_cpus(struct cpufreq_affected_cpus *any)559{560 cpufreq_put_affected_cpus(any);561}562 563static int verify_gov(char *new_gov, char *passed_gov)564{565 unsigned int i, j = 0;566 567 if (!passed_gov || (strlen(passed_gov) > 19))568 return -EINVAL;569 570 strncpy(new_gov, passed_gov, 20);571 for (i = 0; i < 20; i++) {572 if (j) {573 new_gov[i] = '\0';574 continue;575 }576 if ((new_gov[i] >= 'a') && (new_gov[i] <= 'z'))577 continue;578 579 if ((new_gov[i] >= 'A') && (new_gov[i] <= 'Z'))580 continue;581 582 if (new_gov[i] == '-')583 continue;584 585 if (new_gov[i] == '_')586 continue;587 588 if (new_gov[i] == '\0') {589 j = 1;590 continue;591 }592 return -EINVAL;593 }594 new_gov[19] = '\0';595 return 0;596}597 598int cpufreq_set_policy(unsigned int cpu, struct cpufreq_policy *policy)599{600 char min[SYSFS_PATH_MAX];601 char max[SYSFS_PATH_MAX];602 char gov[SYSFS_PATH_MAX];603 int ret;604 unsigned long old_min;605 int write_max_first;606 607 if (!policy || !(policy->governor))608 return -EINVAL;609 610 if (policy->max < policy->min)611 return -EINVAL;612 613 if (verify_gov(gov, policy->governor))614 return -EINVAL;615 616 snprintf(min, SYSFS_PATH_MAX, "%lu", policy->min);617 snprintf(max, SYSFS_PATH_MAX, "%lu", policy->max);618 619 old_min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ);620 write_max_first = (old_min && (policy->max < old_min) ? 0 : 1);621 622 if (write_max_first) {623 ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,624 max, strlen(max));625 if (ret)626 return ret;627 }628 629 ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ, min,630 strlen(min));631 if (ret)632 return ret;633 634 if (!write_max_first) {635 ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,636 max, strlen(max));637 if (ret)638 return ret;639 }640 641 return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR,642 gov, strlen(gov));643}644 645 646int cpufreq_modify_policy_min(unsigned int cpu, unsigned long min_freq)647{648 char value[SYSFS_PATH_MAX];649 650 snprintf(value, SYSFS_PATH_MAX, "%lu", min_freq);651 652 return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ,653 value, strlen(value));654}655 656 657int cpufreq_modify_policy_max(unsigned int cpu, unsigned long max_freq)658{659 char value[SYSFS_PATH_MAX];660 661 snprintf(value, SYSFS_PATH_MAX, "%lu", max_freq);662 663 return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,664 value, strlen(value));665}666 667int cpufreq_modify_policy_governor(unsigned int cpu, char *governor)668{669 char new_gov[SYSFS_PATH_MAX];670 671 if ((!governor) || (strlen(governor) > 19))672 return -EINVAL;673 674 if (verify_gov(new_gov, governor))675 return -EINVAL;676 677 return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR,678 new_gov, strlen(new_gov));679}680 681int cpufreq_set_frequency(unsigned int cpu, unsigned long target_frequency)682{683 struct cpufreq_policy *pol = cpufreq_get_policy(cpu);684 char userspace_gov[] = "userspace";685 char freq[SYSFS_PATH_MAX];686 int ret;687 688 if (!pol)689 return -ENODEV;690 691 if (strncmp(pol->governor, userspace_gov, 9) != 0) {692 ret = cpufreq_modify_policy_governor(cpu, userspace_gov);693 if (ret) {694 cpufreq_put_policy(pol);695 return ret;696 }697 }698 699 cpufreq_put_policy(pol);700 701 snprintf(freq, SYSFS_PATH_MAX, "%lu", target_frequency);702 703 return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_SET_SPEED,704 freq, strlen(freq));705}706 707struct cpufreq_stats *cpufreq_get_stats(unsigned int cpu,708 unsigned long long *total_time)709{710 struct cpufreq_stats *first = NULL;711 struct cpufreq_stats *current = NULL;712 char one_value[SYSFS_PATH_MAX];713 char linebuf[MAX_LINE_LEN];714 unsigned int pos, i;715 unsigned int len;716 717 len = sysfs_cpufreq_read_file(cpu, "stats/time_in_state",718 linebuf, sizeof(linebuf));719 if (len == 0)720 return NULL;721 722 *total_time = 0;723 pos = 0;724 for (i = 0; i < len; i++) {725 if (i == strlen(linebuf) || linebuf[i] == '\n') {726 if (i - pos < 2)727 continue;728 if ((i - pos) >= SYSFS_PATH_MAX)729 goto error_out;730 if (current) {731 current->next = malloc(sizeof(*current));732 if (!current->next)733 goto error_out;734 current = current->next;735 } else {736 first = malloc(sizeof(*first));737 if (!first)738 return NULL;739 current = first;740 }741 current->first = first;742 current->next = NULL;743 744 memcpy(one_value, linebuf + pos, i - pos);745 one_value[i - pos] = '\0';746 if (sscanf(one_value, "%lu %llu",747 ¤t->frequency,748 ¤t->time_in_state) != 2)749 goto error_out;750 751 *total_time = *total_time + current->time_in_state;752 pos = i + 1;753 }754 }755 756 return first;757 758 error_out:759 while (first) {760 current = first->next;761 free(first);762 first = current;763 }764 return NULL;765}766 767void cpufreq_put_stats(struct cpufreq_stats *any)768{769 struct cpufreq_stats *tmp, *next;770 771 if (!any)772 return;773 774 tmp = any->first;775 while (tmp) {776 next = tmp->next;777 free(tmp);778 tmp = next;779 }780}781 782unsigned long cpufreq_get_transitions(unsigned int cpu)783{784 return sysfs_cpufreq_get_one_value(cpu, STATS_NUM_TRANSITIONS);785}786