1/*
2 *  (C) 2004-2009  Dominik Brodowski <linux@dominikbrodowski.de>
3 *
4 *  Licensed under the terms of the GNU GPL License version 2.
5 */
6
7
8#include <unistd.h>
9#include <stdio.h>
10#include <errno.h>
11#include <stdlib.h>
12#include <limits.h>
13#include <string.h>
14#include <ctype.h>
15
16#include <getopt.h>
17
18#include "cpufreq.h"
19#include "helpers/helpers.h"
20#include "helpers/sysfs.h"
21
22#define NORM_FREQ_LEN 32
23
24static struct option set_opts[] = {
25	{"min",		required_argument,	NULL, 'd'},
26	{"max",		required_argument,	NULL, 'u'},
27	{"governor",	required_argument,	NULL, 'g'},
28	{"freq",	required_argument,	NULL, 'f'},
29	{"related",	no_argument,		NULL, 'r'},
30	{ },
31};
32
33static void print_error(void)
34{
35	printf(_("Error setting new values. Common errors:\n"
36			"- Do you have proper administration rights? (super-user?)\n"
37			"- Is the governor you requested available and modprobed?\n"
38			"- Trying to set an invalid policy?\n"
39			"- Trying to set a specific frequency, but userspace governor is not available,\n"
40			"   for example because of hardware which cannot be set to a specific frequency\n"
41			"   or because the userspace governor isn't loaded?\n"));
42};
43
44struct freq_units {
45	char		*str_unit;
46	int		power_of_ten;
47};
48
49const struct freq_units def_units[] = {
50	{"hz", -3},
51	{"khz", 0}, /* default */
52	{"mhz", 3},
53	{"ghz", 6},
54	{"thz", 9},
55	{NULL, 0}
56};
57
58static void print_unknown_arg(void)
59{
60	printf(_("invalid or unknown argument\n"));
61}
62
63static unsigned long string_to_frequency(const char *str)
64{
65	char normalized[NORM_FREQ_LEN];
66	const struct freq_units *unit;
67	const char *scan;
68	char *end;
69	unsigned long freq;
70	int power = 0, match_count = 0, i, cp, pad;
71
72	while (*str == '0')
73		str++;
74
75	for (scan = str; isdigit(*scan) || *scan == '.'; scan++) {
76		if (*scan == '.' && match_count == 0)
77			match_count = 1;
78		else if (*scan == '.' && match_count == 1)
79			return 0;
80	}
81
82	if (*scan) {
83		match_count = 0;
84		for (unit = def_units; unit->str_unit; unit++) {
85			for (i = 0;
86			     scan[i] && tolower(scan[i]) == unit->str_unit[i];
87			     ++i)
88				continue;
89			if (scan[i])
90				continue;
91			match_count++;
92			power = unit->power_of_ten;
93		}
94		if (match_count != 1)
95			return 0;
96	}
97
98	/* count the number of digits to be copied */
99	for (cp = 0; isdigit(str[cp]); cp++)
100		continue;
101
102	if (str[cp] == '.') {
103		while (power > -1 && isdigit(str[cp+1]))
104			cp++, power--;
105	}
106	if (power >= -1)	/* not enough => pad */
107		pad = power + 1;
108	else			/* to much => strip */
109		pad = 0, cp += power + 1;
110	/* check bounds */
111	if (cp <= 0 || cp + pad > NORM_FREQ_LEN - 1)
112		return 0;
113
114	/* copy digits */
115	for (i = 0; i < cp; i++, str++) {
116		if (*str == '.')
117			str++;
118		normalized[i] = *str;
119	}
120	/* and pad */
121	for (; i < cp + pad; i++)
122		normalized[i] = '0';
123
124	/* round up, down ? */
125	match_count = (normalized[i-1] >= '5');
126	/* and drop the decimal part */
127	normalized[i-1] = 0; /* cp > 0 && pad >= 0 ==> i > 0 */
128
129	/* final conversion (and applying rounding) */
130	errno = 0;
131	freq = strtoul(normalized, &end, 10);
132	if (errno)
133		return 0;
134	else {
135		if (match_count && freq != ULONG_MAX)
136			freq++;
137		return freq;
138	}
139}
140
141static int do_new_policy(unsigned int cpu, struct cpufreq_policy *new_pol)
142{
143	struct cpufreq_policy *cur_pol = cpufreq_get_policy(cpu);
144	int ret;
145
146	if (!cur_pol) {
147		printf(_("wrong, unknown or unhandled CPU?\n"));
148		return -EINVAL;
149	}
150
151	if (!new_pol->min)
152		new_pol->min = cur_pol->min;
153
154	if (!new_pol->max)
155		new_pol->max = cur_pol->max;
156
157	if (!new_pol->governor)
158		new_pol->governor = cur_pol->governor;
159
160	ret = cpufreq_set_policy(cpu, new_pol);
161
162	cpufreq_put_policy(cur_pol);
163
164	return ret;
165}
166
167
168static int do_one_cpu(unsigned int cpu, struct cpufreq_policy *new_pol,
169		unsigned long freq, unsigned int pc)
170{
171	switch (pc) {
172	case 0:
173		return cpufreq_set_frequency(cpu, freq);
174
175	case 1:
176		/* if only one value of a policy is to be changed, we can
177		 * use a "fast path".
178		 */
179		if (new_pol->min)
180			return cpufreq_modify_policy_min(cpu, new_pol->min);
181		else if (new_pol->max)
182			return cpufreq_modify_policy_max(cpu, new_pol->max);
183		else if (new_pol->governor)
184			return cpufreq_modify_policy_governor(cpu,
185							new_pol->governor);
186
187	default:
188		/* slow path */
189		return do_new_policy(cpu, new_pol);
190	}
191}
192
193int cmd_freq_set(int argc, char **argv)
194{
195	extern char *optarg;
196	extern int optind, opterr, optopt;
197	int ret = 0, cont = 1;
198	int double_parm = 0, related = 0, policychange = 0;
199	unsigned long freq = 0;
200	char gov[20];
201	unsigned int cpu;
202
203	struct cpufreq_policy new_pol = {
204		.min = 0,
205		.max = 0,
206		.governor = NULL,
207	};
208
209	/* parameter parsing */
210	do {
211		ret = getopt_long(argc, argv, "d:u:g:f:r", set_opts, NULL);
212		switch (ret) {
213		case '?':
214			print_unknown_arg();
215			return -EINVAL;
216		case -1:
217			cont = 0;
218			break;
219		case 'r':
220			if (related)
221				double_parm++;
222			related++;
223			break;
224		case 'd':
225			if (new_pol.min)
226				double_parm++;
227			policychange++;
228			new_pol.min = string_to_frequency(optarg);
229			if (new_pol.min == 0) {
230				print_unknown_arg();
231				return -EINVAL;
232			}
233			break;
234		case 'u':
235			if (new_pol.max)
236				double_parm++;
237			policychange++;
238			new_pol.max = string_to_frequency(optarg);
239			if (new_pol.max == 0) {
240				print_unknown_arg();
241				return -EINVAL;
242			}
243			break;
244		case 'f':
245			if (freq)
246				double_parm++;
247			freq = string_to_frequency(optarg);
248			if (freq == 0) {
249				print_unknown_arg();
250				return -EINVAL;
251			}
252			break;
253		case 'g':
254			if (new_pol.governor)
255				double_parm++;
256			policychange++;
257			if ((strlen(optarg) < 3) || (strlen(optarg) > 18)) {
258				print_unknown_arg();
259				return -EINVAL;
260			}
261			if ((sscanf(optarg, "%19s", gov)) != 1) {
262				print_unknown_arg();
263				return -EINVAL;
264			}
265			new_pol.governor = gov;
266			break;
267		}
268	} while (cont);
269
270	/* parameter checking */
271	if (double_parm) {
272		printf("the same parameter was passed more than once\n");
273		return -EINVAL;
274	}
275
276	if (freq && policychange) {
277		printf(_("the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
278				"-g/--governor parameters\n"));
279		return -EINVAL;
280	}
281
282	if (!freq && !policychange) {
283		printf(_("At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
284				"-g/--governor must be passed\n"));
285		return -EINVAL;
286	}
287
288	/* Default is: set all CPUs */
289	if (bitmask_isallclear(cpus_chosen))
290		bitmask_setall(cpus_chosen);
291
292	/* Also set frequency settings for related CPUs if -r is passed */
293	if (related) {
294		for (cpu = bitmask_first(cpus_chosen);
295		     cpu <= bitmask_last(cpus_chosen); cpu++) {
296			struct cpufreq_affected_cpus *cpus;
297
298			if (!bitmask_isbitset(cpus_chosen, cpu) ||
299			    cpufreq_cpu_exists(cpu))
300				continue;
301
302			cpus = cpufreq_get_related_cpus(cpu);
303			if (!cpus)
304				break;
305			while (cpus->next) {
306				bitmask_setbit(cpus_chosen, cpus->cpu);
307				cpus = cpus->next;
308			}
309			cpufreq_put_related_cpus(cpus);
310		}
311	}
312
313
314	/* loop over CPUs */
315	for (cpu = bitmask_first(cpus_chosen);
316	     cpu <= bitmask_last(cpus_chosen); cpu++) {
317
318		if (!bitmask_isbitset(cpus_chosen, cpu) ||
319		    cpufreq_cpu_exists(cpu))
320			continue;
321
322		if (sysfs_is_cpu_online(cpu) != 1)
323			continue;
324
325		printf(_("Setting cpu: %d\n"), cpu);
326		ret = do_one_cpu(cpu, &new_pol, freq, policychange);
327		if (ret) {
328			print_error();
329			return ret;
330		}
331	}
332
333	return 0;
334}
335