// SPDX-License-Identifier: GPL-2.0-only /* * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> */ #include <unistd.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <limits.h> #include <string.h> #include <ctype.h> #include <getopt.h> #include "cpufreq.h" #include "cpuidle.h" #include "helpers/helpers.h" #define NORM_FREQ_LEN 32 static struct option set_opts[] = { {"min", required_argument, NULL, 'd'}, {"max", required_argument, NULL, 'u'}, {"governor", required_argument, NULL, 'g'}, {"freq", required_argument, NULL, 'f'}, {"related", no_argument, NULL, 'r'}, { }, }; static void print_error(void) { printf(_("Error setting new values. Common errors:\n" "- Do you have proper administration rights? (super-user?)\n" "- Is the governor you requested available and modprobed?\n" "- Trying to set an invalid policy?\n" "- Trying to set a specific frequency, but userspace governor is not available,\n" " for example because of hardware which cannot be set to a specific frequency\n" " or because the userspace governor isn't loaded?\n")); }; struct freq_units { char *str_unit; int power_of_ten; }; const struct freq_units def_units[] = { {"hz", -3}, {"khz", 0}, /* default */ {"mhz", 3}, {"ghz", 6}, {"thz", 9}, {NULL, 0} }; static void print_unknown_arg(void) { printf(_("invalid or unknown argument\n")); } static unsigned long string_to_frequency(const char *str) { char normalized[NORM_FREQ_LEN]; const struct freq_units *unit; const char *scan; char *end; unsigned long freq; int power = 0, match_count = 0, i, cp, pad; while (*str == '0') str++; for (scan = str; isdigit(*scan) || *scan == '.'; scan++) { if (*scan == '.' && match_count == 0) match_count = 1; else if (*scan == '.' && match_count == 1) return 0; } if (*scan) { match_count = 0; for (unit = def_units; unit->str_unit; unit++) { for (i = 0; scan[i] && tolower(scan[i]) == unit->str_unit[i]; ++i) continue; if (scan[i]) continue; match_count++; power = unit->power_of_ten; } if (match_count != 1) return 0; } /* count the number of digits to be copied */ for (cp = 0; isdigit(str[cp]); cp++) continue; if (str[cp] == '.') { while (power > -1 && isdigit(str[cp+1])) { cp++; power--; } } if (power >= -1) { /* not enough => pad */ pad = power + 1; } else { /* too much => strip */ pad = 0; cp += power + 1; } /* check bounds */ if (cp <= 0 || cp + pad > NORM_FREQ_LEN - 1) return 0; /* copy digits */ for (i = 0; i < cp; i++, str++) { if (*str == '.') str++; normalized[i] = *str; } /* and pad */ for (; i < cp + pad; i++) normalized[i] = '0'; /* round up, down ? */ match_count = (normalized[i-1] >= '5'); /* and drop the decimal part */ normalized[i-1] = 0; /* cp > 0 && pad >= 0 ==> i > 0 */ /* final conversion (and applying rounding) */ errno = 0; freq = strtoul(normalized, &end, 10); if (errno) return 0; else { if (match_count && freq != ULONG_MAX) freq++; return freq; } } static int do_new_policy(unsigned int cpu, struct cpufreq_policy *new_pol) { struct cpufreq_policy *cur_pol = cpufreq_get_policy(cpu); int ret; if (!cur_pol) { printf(_("wrong, unknown or unhandled CPU?\n")); return -EINVAL; } if (!new_pol->min) new_pol->min = cur_pol->min; if (!new_pol->max) new_pol->max = cur_pol->max; if (!new_pol->governor) new_pol->governor = cur_pol->governor; ret = cpufreq_set_policy(cpu, new_pol); cpufreq_put_policy(cur_pol); return ret; } static int do_one_cpu(unsigned int cpu, struct cpufreq_policy *new_pol, unsigned long freq, unsigned int pc) { switch (pc) { case 0: return cpufreq_set_frequency(cpu, freq); case 1: /* if only one value of a policy is to be changed, we can * use a "fast path". */ if (new_pol->min) return cpufreq_modify_policy_min(cpu, new_pol->min); else if (new_pol->max) return cpufreq_modify_policy_max(cpu, new_pol->max); else if (new_pol->governor) return cpufreq_modify_policy_governor(cpu, new_pol->governor); default: /* slow path */ return do_new_policy(cpu, new_pol); } } int cmd_freq_set(int argc, char **argv) { extern char *optarg; extern int optind, opterr, optopt; int ret = 0, cont = 1; int double_parm = 0, related = 0, policychange = 0; unsigned long freq = 0; char gov[20]; unsigned int cpu; struct cpufreq_policy new_pol = { .min = 0, .max = 0, .governor = NULL, }; /* parameter parsing */ do { ret = getopt_long(argc, argv, "d:u:g:f:r", set_opts, NULL); switch (ret) { case '?': print_unknown_arg(); return -EINVAL; case -1: cont = 0; break; case 'r': if (related) double_parm++; related++; break; case 'd': if (new_pol.min) double_parm++; policychange++; new_pol.min = string_to_frequency(optarg); if (new_pol.min == 0) { print_unknown_arg(); return -EINVAL; } break; case 'u': if (new_pol.max) double_parm++; policychange++; new_pol.max = string_to_frequency(optarg); if (new_pol.max == 0) { print_unknown_arg(); return -EINVAL; } break; case 'f': if (freq) double_parm++; freq = string_to_frequency(optarg); if (freq == 0) { print_unknown_arg(); return -EINVAL; } break; case 'g': if (new_pol.governor) double_parm++; policychange++; if ((strlen(optarg) < 3) || (strlen(optarg) > 18)) { print_unknown_arg(); return -EINVAL; } if ((sscanf(optarg, "%19s", gov)) != 1) { print_unknown_arg(); return -EINVAL; } new_pol.governor = gov; break; } } while (cont); /* parameter checking */ if (double_parm) { printf("the same parameter was passed more than once\n"); return -EINVAL; } if (freq && policychange) { printf(_("the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n" "-g/--governor parameters\n")); return -EINVAL; } if (!freq && !policychange) { printf(_("At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n" "-g/--governor must be passed\n")); return -EINVAL; } /* Default is: set all CPUs */ if (bitmask_isallclear(cpus_chosen)) bitmask_setall(cpus_chosen); /* Also set frequency settings for related CPUs if -r is passed */ if (related) { for (cpu = bitmask_first(cpus_chosen); cpu <= bitmask_last(cpus_chosen); cpu++) { struct cpufreq_affected_cpus *cpus; if (!bitmask_isbitset(cpus_chosen, cpu) || cpupower_is_cpu_online(cpu) != 1) continue; cpus = cpufreq_get_related_cpus(cpu); if (!cpus) break; while (cpus->next) { bitmask_setbit(cpus_chosen, cpus->cpu); cpus = cpus->next; } /* Set the last cpu in related cpus list */ bitmask_setbit(cpus_chosen, cpus->cpu); cpufreq_put_related_cpus(cpus); } } get_cpustate(); /* loop over CPUs */ for (cpu = bitmask_first(cpus_chosen); cpu <= bitmask_last(cpus_chosen); cpu++) { if (!bitmask_isbitset(cpus_chosen, cpu) || cpupower_is_cpu_online(cpu) != 1) continue; printf(_("Setting cpu: %d\n"), cpu); ret = do_one_cpu(cpu, &new_pol, freq, policychange); if (ret) { print_error(); return ret; } } print_offline_cpus(); return 0; }