Commit | Line | Data |
---|---|---|
7fe2f639 DB |
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" | |
ac5a181d | 19 | #include "cpuidle.h" |
7fe2f639 DB |
20 | #include "helpers/helpers.h" |
21 | ||
22 | #define NORM_FREQ_LEN 32 | |
23 | ||
7fe2f639 | 24 | static struct option set_opts[] = { |
57ab3b08 SR |
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'}, | |
7fe2f639 DB |
30 | { }, |
31 | }; | |
32 | ||
33 | static 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 | ||
44 | struct freq_units { | |
a1ce5ba2 | 45 | char *str_unit; |
7fe2f639 DB |
46 | int power_of_ten; |
47 | }; | |
48 | ||
49 | const 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 | ||
58 | static void print_unknown_arg(void) | |
59 | { | |
60 | printf(_("invalid or unknown argument\n")); | |
7fe2f639 DB |
61 | } |
62 | ||
63 | static 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 | ||
141 | static 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 | ||
168 | static 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) | |
a1ce5ba2 DB |
184 | return cpufreq_modify_policy_governor(cpu, |
185 | new_pol->governor); | |
7fe2f639 DB |
186 | |
187 | default: | |
188 | /* slow path */ | |
189 | return do_new_policy(cpu, new_pol); | |
190 | } | |
191 | } | |
192 | ||
193 | int 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 { | |
498ca793 | 211 | ret = getopt_long(argc, argv, "d:u:g:f:r", set_opts, NULL); |
7fe2f639 DB |
212 | switch (ret) { |
213 | case '?': | |
214 | print_unknown_arg(); | |
215 | return -EINVAL; | |
7fe2f639 DB |
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; | |
a1ce5ba2 | 260 | } |
fdfe840e | 261 | if ((sscanf(optarg, "%19s", gov)) != 1) { |
7fe2f639 DB |
262 | print_unknown_arg(); |
263 | return -EINVAL; | |
a1ce5ba2 | 264 | } |
7fe2f639 DB |
265 | new_pol.governor = gov; |
266 | break; | |
267 | } | |
a1ce5ba2 | 268 | } while (cont); |
7fe2f639 DB |
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) || | |
ac5a181d | 299 | cpupower_is_cpu_online(cpu)) |
7fe2f639 DB |
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++) { | |
a1ce5ba2 | 317 | |
7fe2f639 | 318 | if (!bitmask_isbitset(cpus_chosen, cpu) || |
ac5a181d | 319 | cpupower_is_cpu_online(cpu)) |
7fe2f639 DB |
320 | continue; |
321 | ||
ac5a181d | 322 | if (cpupower_is_cpu_online(cpu) != 1) |
2e5e8fd1 SB |
323 | continue; |
324 | ||
7fe2f639 DB |
325 | printf(_("Setting cpu: %d\n"), cpu); |
326 | ret = do_one_cpu(cpu, &new_pol, freq, policychange); | |
059802f9 PST |
327 | if (ret) { |
328 | print_error(); | |
329 | return ret; | |
330 | } | |
7fe2f639 DB |
331 | } |
332 | ||
059802f9 | 333 | return 0; |
7fe2f639 | 334 | } |