| /* | |
| * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> | |
| * | |
| * Licensed under the terms of the GNU GPL License version 2. | |
| */ | |
| #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 /* to 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; | |
| } | |
| cpufreq_put_related_cpus(cpus); | |
| } | |
| } | |
| /* 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; | |
| } | |
| } | |
| return 0; | |
| } |