| /* |
| * (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 "helpers/helpers.h" |
| #include "helpers/sysfs.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) || |
| cpufreq_cpu_exists(cpu)) |
| 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); |
| } |
| } |
| |
| |
| /* loop over CPUs */ |
| for (cpu = bitmask_first(cpus_chosen); |
| cpu <= bitmask_last(cpus_chosen); cpu++) { |
| |
| if (!bitmask_isbitset(cpus_chosen, cpu) || |
| cpufreq_cpu_exists(cpu)) |
| continue; |
| |
| if (sysfs_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; |
| } |