blob: 2ece47e46115d30a4fc37e0c4f39d66185b53dfb [file] [log] [blame]
/*
* (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 <libintl.h>
#include <locale.h>
#include <getopt.h>
#include "cpufreq.h"
#define _(String) gettext(String)
#define gettext_noop(String) String
#define N_(String) gettext_noop(String)
#define NORM_FREQ_LEN 32
static void print_header(void)
{
printf(PACKAGE " " VERSION ": cpufreq-set (C) Dominik Brodowski 2004-2009\n");
printf(gettext("Report errors and bugs to %s, please.\n"), PACKAGE_BUGREPORT);
}
static void print_help(void)
{
printf(gettext("Usage: cpufreq-set [options]\n"));
printf(gettext("Options:\n"));
printf(gettext(" -c CPU, --cpu CPU number of CPU where cpufreq settings shall be modified\n"));
printf(gettext(" -d FREQ, --min FREQ new minimum CPU frequency the governor may select\n"));
printf(gettext(" -u FREQ, --max FREQ new maximum CPU frequency the governor may select\n"));
printf(gettext(" -g GOV, --governor GOV new cpufreq governor\n"));
printf(gettext(" -f FREQ, --freq FREQ specific frequency to be set. Requires userspace\n"
" governor to be available and loaded\n"));
printf(gettext(" -r, --related Switches all hardware-related CPUs\n"));
printf(gettext(" -h, --help Prints out this screen\n"));
printf("\n");
printf(gettext("Notes:\n"
"1. Omitting the -c or --cpu argument is equivalent to setting it to zero\n"
"2. The -f FREQ, --freq FREQ parameter cannot be combined with any other parameter\n"
" except the -c CPU, --cpu CPU parameter\n"
"3. FREQuencies can be passed in Hz, kHz (default), MHz, GHz, or THz\n"
" by postfixing the value with the wanted unit name, without any space\n"
" (FREQuency in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"));
}
static struct option set_opts[] = {
{ .name="cpu", .has_arg=required_argument, .flag=NULL, .val='c'},
{ .name="min", .has_arg=required_argument, .flag=NULL, .val='d'},
{ .name="max", .has_arg=required_argument, .flag=NULL, .val='u'},
{ .name="governor", .has_arg=required_argument, .flag=NULL, .val='g'},
{ .name="freq", .has_arg=required_argument, .flag=NULL, .val='f'},
{ .name="help", .has_arg=no_argument, .flag=NULL, .val='h'},
{ .name="related", .has_arg=no_argument, .flag=NULL, .val='r'},
};
static void print_error(void)
{
printf(gettext("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)
{
print_header();
printf(gettext("invalid or unknown argument\n"));
print_help();
}
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(gettext("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 main(int argc, char **argv)
{
extern char *optarg;
extern int optind, opterr, optopt;
int ret = 0, cont = 1;
unsigned long freq = 0;
char gov[20];
int double_parm = 0;
int related = 0;
int policychange = 0;
struct cpufreq_policy new_pol = {
.min = 0,
.max = 0,
.governor = NULL,
};
struct cpufreq_affected_cpus single_cpu = {
.cpu = 0,
.next = NULL,
.first = &single_cpu,
};
struct cpufreq_affected_cpus *cpus = NULL;
setlocale(LC_ALL, "");
textdomain (PACKAGE);
/* parameter parsing */
do {
ret = getopt_long(argc, argv, "c:d:u:g:f:hr", set_opts, NULL);
switch (ret) {
case '?':
print_unknown_arg();
return -EINVAL;
case 'h':
print_header();
print_help();
return 0;
case -1:
cont = 0;
break;
case 'r':
if (related)
double_parm++;
related++;
break;
case 'c':
if (cpus)
double_parm++;
cpus = &single_cpu;
if ((sscanf(optarg, "%d ", &single_cpu.cpu)) != 1) {
print_unknown_arg();
return -EINVAL;
}
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, "%s", gov)) != 1) {
print_unknown_arg();
return -EINVAL;
}
new_pol.governor = gov;
break;
}
} while(cont);
/* parameter checking */
if (double_parm) {
print_header();
printf("the same parameter was passed more than once\n");
return -EINVAL;
}
if (freq && policychange) {
printf(gettext("the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
"-g/--governor parameters\n"));
return -EINVAL;
}
if (!freq && !policychange) {
printf(gettext("At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
"-g/--governor must be passed\n"));
return -EINVAL;
}
/* which CPUs shall we modify? */
if (!cpus)
cpus = &single_cpu;
if (related)
cpus = cpufreq_get_related_cpus(cpus->cpu);
/* loop over CPUs */
while (1) {
ret = do_one_cpu(cpus->cpu, &new_pol, freq, policychange);
if (ret)
break;
if (!cpus->next)
break;
cpus = cpus->next;
}
/* cleanup */
if (cpus->first != &single_cpu)
cpufreq_put_related_cpus(cpus->first);
if (ret)
print_error();
return ret;
}