340 lines
7.1 KiB
C
340 lines
7.1 KiB
C
// 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;
|
|
}
|