forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
7.1 KiB
339 lines
7.1 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* (C) 2004-2009 Dominik Brodowski <[email protected]> |
|
*/ |
|
|
|
|
|
#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; |
|
}
|
|
|