1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de> |
4 | */ |
5 | |
6 | |
7 | #include <unistd.h> |
8 | #include <stdio.h> |
9 | #include <errno.h> |
10 | #include <stdlib.h> |
11 | #include <string.h> |
12 | #include <limits.h> |
13 | |
14 | #include <getopt.h> |
15 | |
16 | #include "cpufreq.h" |
17 | #include "helpers/sysfs.h" |
18 | #include "helpers/helpers.h" |
19 | #include "helpers/bitmask.h" |
20 | |
21 | #define LINE_LEN 10 |
22 | |
23 | static unsigned int count_cpus(void) |
24 | { |
25 | FILE *fp; |
26 | char value[LINE_LEN]; |
27 | unsigned int ret = 0; |
28 | unsigned int cpunr = 0; |
29 | |
30 | fp = fopen("/proc/stat" , "r" ); |
31 | if (!fp) { |
32 | printf(_("Couldn't count the number of CPUs (%s: %s), assuming 1\n" ), "/proc/stat" , strerror(errno)); |
33 | return 1; |
34 | } |
35 | |
36 | while (!feof(fp)) { |
37 | if (!fgets(value, LINE_LEN, fp)) |
38 | continue; |
39 | value[LINE_LEN - 1] = '\0'; |
40 | if (strlen(value) < (LINE_LEN - 2)) |
41 | continue; |
42 | if (strstr(value, "cpu " )) |
43 | continue; |
44 | if (sscanf(value, "cpu%d " , &cpunr) != 1) |
45 | continue; |
46 | if (cpunr > ret) |
47 | ret = cpunr; |
48 | } |
49 | fclose(fp); |
50 | |
51 | /* cpu count starts from 0, on error return 1 (UP) */ |
52 | return ret + 1; |
53 | } |
54 | |
55 | |
56 | static void proc_cpufreq_output(void) |
57 | { |
58 | unsigned int cpu, nr_cpus; |
59 | struct cpufreq_policy *policy; |
60 | unsigned int min_pctg = 0; |
61 | unsigned int max_pctg = 0; |
62 | unsigned long min, max; |
63 | |
64 | printf(_(" minimum CPU frequency - maximum CPU frequency - governor\n" )); |
65 | |
66 | nr_cpus = count_cpus(); |
67 | for (cpu = 0; cpu < nr_cpus; cpu++) { |
68 | policy = cpufreq_get_policy(cpu); |
69 | if (!policy) |
70 | continue; |
71 | |
72 | if (cpufreq_get_hardware_limits(cpu, &min, &max)) { |
73 | max = 0; |
74 | } else { |
75 | min_pctg = (policy->min * 100) / max; |
76 | max_pctg = (policy->max * 100) / max; |
77 | } |
78 | printf("CPU%3d %9lu kHz (%3d %%) - %9lu kHz (%3d %%) - %s\n" , |
79 | cpu , policy->min, max ? min_pctg : 0, policy->max, |
80 | max ? max_pctg : 0, policy->governor); |
81 | |
82 | cpufreq_put_policy(policy); |
83 | } |
84 | } |
85 | |
86 | static int no_rounding; |
87 | static void print_duration(unsigned long duration) |
88 | { |
89 | unsigned long tmp; |
90 | |
91 | if (no_rounding) { |
92 | if (duration > 1000000) |
93 | printf("%u.%06u ms" , ((unsigned int) duration/1000000), |
94 | ((unsigned int) duration%1000000)); |
95 | else if (duration > 100000) |
96 | printf("%u us" , ((unsigned int) duration/1000)); |
97 | else if (duration > 1000) |
98 | printf("%u.%03u us" , ((unsigned int) duration/1000), |
99 | ((unsigned int) duration%1000)); |
100 | else |
101 | printf("%lu ns" , duration); |
102 | } else { |
103 | if (duration > 1000000) { |
104 | tmp = duration%10000; |
105 | if (tmp >= 5000) |
106 | duration += 10000; |
107 | printf("%u.%02u ms" , ((unsigned int) duration/1000000), |
108 | ((unsigned int) (duration%1000000)/10000)); |
109 | } else if (duration > 100000) { |
110 | tmp = duration%1000; |
111 | if (tmp >= 500) |
112 | duration += 1000; |
113 | printf("%u us" , ((unsigned int) duration / 1000)); |
114 | } else if (duration > 1000) { |
115 | tmp = duration%100; |
116 | if (tmp >= 50) |
117 | duration += 100; |
118 | printf("%u.%01u us" , ((unsigned int) duration/1000), |
119 | ((unsigned int) (duration%1000)/100)); |
120 | } else |
121 | printf("%lu ns" , duration); |
122 | } |
123 | return; |
124 | } |
125 | |
126 | static int get_boost_mode_x86(unsigned int cpu) |
127 | { |
128 | int support, active, b_states = 0, ret, pstate_no, i; |
129 | /* ToDo: Make this more global */ |
130 | unsigned long pstates[MAX_HW_PSTATES] = {0,}; |
131 | |
132 | ret = cpufreq_has_boost_support(cpu, support: &support, active: &active, states: &b_states); |
133 | if (ret) { |
134 | printf(_("Error while evaluating Boost Capabilities" |
135 | " on CPU %d -- are you root?\n" ), cpu); |
136 | return ret; |
137 | } |
138 | /* P state changes via MSR are identified via cpuid 80000007 |
139 | on Intel and AMD, but we assume boost capable machines can do that |
140 | if (cpuid_eax(0x80000000) >= 0x80000007 |
141 | && (cpuid_edx(0x80000007) & (1 << 7))) |
142 | */ |
143 | |
144 | printf(_(" boost state support:\n" )); |
145 | |
146 | printf(_(" Supported: %s\n" ), support ? _("yes" ) : _("no" )); |
147 | printf(_(" Active: %s\n" ), active ? _("yes" ) : _("no" )); |
148 | |
149 | if (cpupower_cpu_info.vendor == X86_VENDOR_AMD && |
150 | cpupower_cpu_info.caps & CPUPOWER_CAP_AMD_PSTATE) { |
151 | return 0; |
152 | } else if ((cpupower_cpu_info.vendor == X86_VENDOR_AMD && |
153 | cpupower_cpu_info.family >= 0x10) || |
154 | cpupower_cpu_info.vendor == X86_VENDOR_HYGON) { |
155 | ret = decode_pstates(cpu, boost_states: b_states, pstates, no: &pstate_no); |
156 | if (ret) |
157 | return ret; |
158 | |
159 | printf(_(" Boost States: %d\n" ), b_states); |
160 | printf(_(" Total States: %d\n" ), pstate_no); |
161 | for (i = 0; i < pstate_no; i++) { |
162 | if (!pstates[i]) |
163 | continue; |
164 | if (i < b_states) |
165 | printf(_(" Pstate-Pb%d: %luMHz (boost state)" |
166 | "\n" ), i, pstates[i]); |
167 | else |
168 | printf(_(" Pstate-P%d: %luMHz\n" ), |
169 | i - b_states, pstates[i]); |
170 | } |
171 | } else if (cpupower_cpu_info.caps & CPUPOWER_CAP_HAS_TURBO_RATIO) { |
172 | double bclk; |
173 | unsigned long long intel_turbo_ratio = 0; |
174 | unsigned int ratio; |
175 | |
176 | /* Any way to autodetect this ? */ |
177 | if (cpupower_cpu_info.caps & CPUPOWER_CAP_IS_SNB) |
178 | bclk = 100.00; |
179 | else |
180 | bclk = 133.33; |
181 | intel_turbo_ratio = msr_intel_get_turbo_ratio(cpu); |
182 | dprint (fmt: " Ratio: 0x%llx - bclk: %f\n" , |
183 | intel_turbo_ratio, bclk); |
184 | |
185 | ratio = (intel_turbo_ratio >> 24) & 0xFF; |
186 | if (ratio) |
187 | printf(_(" %.0f MHz max turbo 4 active cores\n" ), |
188 | ratio * bclk); |
189 | |
190 | ratio = (intel_turbo_ratio >> 16) & 0xFF; |
191 | if (ratio) |
192 | printf(_(" %.0f MHz max turbo 3 active cores\n" ), |
193 | ratio * bclk); |
194 | |
195 | ratio = (intel_turbo_ratio >> 8) & 0xFF; |
196 | if (ratio) |
197 | printf(_(" %.0f MHz max turbo 2 active cores\n" ), |
198 | ratio * bclk); |
199 | |
200 | ratio = (intel_turbo_ratio >> 0) & 0xFF; |
201 | if (ratio) |
202 | printf(_(" %.0f MHz max turbo 1 active cores\n" ), |
203 | ratio * bclk); |
204 | } |
205 | return 0; |
206 | } |
207 | |
208 | /* --boost / -b */ |
209 | |
210 | static int get_boost_mode(unsigned int cpu) |
211 | { |
212 | struct cpufreq_available_frequencies *freqs; |
213 | |
214 | if (cpupower_cpu_info.vendor == X86_VENDOR_AMD || |
215 | cpupower_cpu_info.vendor == X86_VENDOR_HYGON || |
216 | cpupower_cpu_info.vendor == X86_VENDOR_INTEL) |
217 | return get_boost_mode_x86(cpu); |
218 | |
219 | freqs = cpufreq_get_boost_frequencies(cpu); |
220 | if (freqs) { |
221 | printf(_(" boost frequency steps: " )); |
222 | while (freqs->next) { |
223 | print_speed(speed: freqs->frequency, no_rounding); |
224 | printf(", " ); |
225 | freqs = freqs->next; |
226 | } |
227 | print_speed(speed: freqs->frequency, no_rounding); |
228 | printf("\n" ); |
229 | cpufreq_put_available_frequencies(freqs); |
230 | } |
231 | |
232 | return 0; |
233 | } |
234 | |
235 | /* --freq / -f */ |
236 | |
237 | static int get_freq_kernel(unsigned int cpu, unsigned int human) |
238 | { |
239 | unsigned long freq = cpufreq_get_freq_kernel(cpu); |
240 | printf(_(" current CPU frequency: " )); |
241 | if (!freq) { |
242 | printf(_(" Unable to call to kernel\n" )); |
243 | return -EINVAL; |
244 | } |
245 | if (human) { |
246 | print_speed(speed: freq, no_rounding); |
247 | } else |
248 | printf("%lu" , freq); |
249 | printf(_(" (asserted by call to kernel)\n" )); |
250 | return 0; |
251 | } |
252 | |
253 | |
254 | /* --hwfreq / -w */ |
255 | |
256 | static int get_freq_hardware(unsigned int cpu, unsigned int human) |
257 | { |
258 | unsigned long freq = cpufreq_get_freq_hardware(cpu); |
259 | printf(_(" current CPU frequency: " )); |
260 | if (!freq) { |
261 | printf("Unable to call hardware\n" ); |
262 | return -EINVAL; |
263 | } |
264 | if (human) { |
265 | print_speed(speed: freq, no_rounding); |
266 | } else |
267 | printf("%lu" , freq); |
268 | printf(_(" (asserted by call to hardware)\n" )); |
269 | return 0; |
270 | } |
271 | |
272 | /* --hwlimits / -l */ |
273 | |
274 | static int get_hardware_limits(unsigned int cpu, unsigned int human) |
275 | { |
276 | unsigned long min, max; |
277 | |
278 | if (cpufreq_get_hardware_limits(cpu, &min, &max)) { |
279 | printf(_("Not Available\n" )); |
280 | return -EINVAL; |
281 | } |
282 | |
283 | if (human) { |
284 | printf(_(" hardware limits: " )); |
285 | print_speed(speed: min, no_rounding); |
286 | printf(" - " ); |
287 | print_speed(speed: max, no_rounding); |
288 | printf("\n" ); |
289 | } else { |
290 | printf("%lu %lu\n" , min, max); |
291 | } |
292 | return 0; |
293 | } |
294 | |
295 | /* --driver / -d */ |
296 | |
297 | static int get_driver(unsigned int cpu) |
298 | { |
299 | char *driver = cpufreq_get_driver(cpu); |
300 | if (!driver) { |
301 | printf(_(" no or unknown cpufreq driver is active on this CPU\n" )); |
302 | return -EINVAL; |
303 | } |
304 | printf(" driver: %s\n" , driver); |
305 | cpufreq_put_driver(driver); |
306 | return 0; |
307 | } |
308 | |
309 | /* --policy / -p */ |
310 | |
311 | static int get_policy(unsigned int cpu) |
312 | { |
313 | struct cpufreq_policy *policy = cpufreq_get_policy(cpu); |
314 | if (!policy) { |
315 | printf(_(" Unable to determine current policy\n" )); |
316 | return -EINVAL; |
317 | } |
318 | printf(_(" current policy: frequency should be within " )); |
319 | print_speed(speed: policy->min, no_rounding); |
320 | printf(_(" and " )); |
321 | print_speed(speed: policy->max, no_rounding); |
322 | |
323 | printf(".\n " ); |
324 | printf(_("The governor \"%s\" may decide which speed to use\n" |
325 | " within this range.\n" ), |
326 | policy->governor); |
327 | cpufreq_put_policy(policy); |
328 | return 0; |
329 | } |
330 | |
331 | /* --governors / -g */ |
332 | |
333 | static int get_available_governors(unsigned int cpu) |
334 | { |
335 | struct cpufreq_available_governors *governors = |
336 | cpufreq_get_available_governors(cpu); |
337 | |
338 | printf(_(" available cpufreq governors: " )); |
339 | if (!governors) { |
340 | printf(_("Not Available\n" )); |
341 | return -EINVAL; |
342 | } |
343 | |
344 | while (governors->next) { |
345 | printf("%s " , governors->governor); |
346 | governors = governors->next; |
347 | } |
348 | printf("%s\n" , governors->governor); |
349 | cpufreq_put_available_governors(governors); |
350 | return 0; |
351 | } |
352 | |
353 | |
354 | /* --affected-cpus / -a */ |
355 | |
356 | static int get_affected_cpus(unsigned int cpu) |
357 | { |
358 | struct cpufreq_affected_cpus *cpus = cpufreq_get_affected_cpus(cpu); |
359 | |
360 | printf(_(" CPUs which need to have their frequency coordinated by software: " )); |
361 | if (!cpus) { |
362 | printf(_("Not Available\n" )); |
363 | return -EINVAL; |
364 | } |
365 | |
366 | while (cpus->next) { |
367 | printf("%d " , cpus->cpu); |
368 | cpus = cpus->next; |
369 | } |
370 | printf("%d\n" , cpus->cpu); |
371 | cpufreq_put_affected_cpus(cpus); |
372 | return 0; |
373 | } |
374 | |
375 | /* --related-cpus / -r */ |
376 | |
377 | static int get_related_cpus(unsigned int cpu) |
378 | { |
379 | struct cpufreq_affected_cpus *cpus = cpufreq_get_related_cpus(cpu); |
380 | |
381 | printf(_(" CPUs which run at the same hardware frequency: " )); |
382 | if (!cpus) { |
383 | printf(_("Not Available\n" )); |
384 | return -EINVAL; |
385 | } |
386 | |
387 | while (cpus->next) { |
388 | printf("%d " , cpus->cpu); |
389 | cpus = cpus->next; |
390 | } |
391 | printf("%d\n" , cpus->cpu); |
392 | cpufreq_put_related_cpus(cpus); |
393 | return 0; |
394 | } |
395 | |
396 | /* --stats / -s */ |
397 | |
398 | static int get_freq_stats(unsigned int cpu, unsigned int human) |
399 | { |
400 | unsigned long total_trans = cpufreq_get_transitions(cpu); |
401 | unsigned long long total_time; |
402 | struct cpufreq_stats *stats = cpufreq_get_stats(cpu, &total_time); |
403 | while (stats) { |
404 | if (human) { |
405 | print_speed(speed: stats->frequency, no_rounding); |
406 | printf(":%.2f%%" , |
407 | (100.0 * stats->time_in_state) / total_time); |
408 | } else |
409 | printf("%lu:%llu" , |
410 | stats->frequency, stats->time_in_state); |
411 | stats = stats->next; |
412 | if (stats) |
413 | printf(", " ); |
414 | } |
415 | cpufreq_put_stats(stats); |
416 | if (total_trans) |
417 | printf(" (%lu)\n" , total_trans); |
418 | return 0; |
419 | } |
420 | |
421 | /* --latency / -y */ |
422 | |
423 | static int get_latency(unsigned int cpu, unsigned int human) |
424 | { |
425 | unsigned long latency = cpufreq_get_transition_latency(cpu); |
426 | |
427 | printf(_(" maximum transition latency: " )); |
428 | if (!latency || latency == UINT_MAX) { |
429 | printf(_(" Cannot determine or is not supported.\n" )); |
430 | return -EINVAL; |
431 | } |
432 | |
433 | if (human) { |
434 | print_duration(duration: latency); |
435 | printf("\n" ); |
436 | } else |
437 | printf("%lu\n" , latency); |
438 | return 0; |
439 | } |
440 | |
441 | /* --performance / -c */ |
442 | |
443 | static int get_perf_cap(unsigned int cpu) |
444 | { |
445 | if (cpupower_cpu_info.vendor == X86_VENDOR_AMD && |
446 | cpupower_cpu_info.caps & CPUPOWER_CAP_AMD_PSTATE) |
447 | amd_pstate_show_perf_and_freq(cpu, no_rounding); |
448 | |
449 | return 0; |
450 | } |
451 | |
452 | static void debug_output_one(unsigned int cpu) |
453 | { |
454 | struct cpufreq_available_frequencies *freqs; |
455 | |
456 | get_driver(cpu); |
457 | get_related_cpus(cpu); |
458 | get_affected_cpus(cpu); |
459 | get_latency(cpu, human: 1); |
460 | get_hardware_limits(cpu, human: 1); |
461 | |
462 | freqs = cpufreq_get_available_frequencies(cpu); |
463 | if (freqs) { |
464 | printf(_(" available frequency steps: " )); |
465 | while (freqs->next) { |
466 | print_speed(speed: freqs->frequency, no_rounding); |
467 | printf(", " ); |
468 | freqs = freqs->next; |
469 | } |
470 | print_speed(speed: freqs->frequency, no_rounding); |
471 | printf("\n" ); |
472 | cpufreq_put_available_frequencies(freqs); |
473 | } |
474 | |
475 | get_available_governors(cpu); |
476 | get_policy(cpu); |
477 | if (get_freq_hardware(cpu, human: 1) < 0) |
478 | get_freq_kernel(cpu, human: 1); |
479 | get_boost_mode(cpu); |
480 | get_perf_cap(cpu); |
481 | } |
482 | |
483 | static struct option info_opts[] = { |
484 | {"debug" , no_argument, NULL, 'e'}, |
485 | {"boost" , no_argument, NULL, 'b'}, |
486 | {"freq" , no_argument, NULL, 'f'}, |
487 | {"hwfreq" , no_argument, NULL, 'w'}, |
488 | {"hwlimits" , no_argument, NULL, 'l'}, |
489 | {"driver" , no_argument, NULL, 'd'}, |
490 | {"policy" , no_argument, NULL, 'p'}, |
491 | {"governors" , no_argument, NULL, 'g'}, |
492 | {"related-cpus" , no_argument, NULL, 'r'}, |
493 | {"affected-cpus" , no_argument, NULL, 'a'}, |
494 | {"stats" , no_argument, NULL, 's'}, |
495 | {"latency" , no_argument, NULL, 'y'}, |
496 | {"proc" , no_argument, NULL, 'o'}, |
497 | {"human" , no_argument, NULL, 'm'}, |
498 | {"no-rounding" , no_argument, NULL, 'n'}, |
499 | {"performance" , no_argument, NULL, 'c'}, |
500 | { }, |
501 | }; |
502 | |
503 | int cmd_freq_info(int argc, char **argv) |
504 | { |
505 | extern char *optarg; |
506 | extern int optind, opterr, optopt; |
507 | int ret = 0, cont = 1; |
508 | unsigned int cpu = 0; |
509 | unsigned int human = 0; |
510 | int output_param = 0; |
511 | |
512 | do { |
513 | ret = getopt_long(argc, argv, "oefwldpgrasmybnc" , info_opts, |
514 | NULL); |
515 | switch (ret) { |
516 | case '?': |
517 | output_param = '?'; |
518 | cont = 0; |
519 | break; |
520 | case -1: |
521 | cont = 0; |
522 | break; |
523 | case 'b': |
524 | case 'o': |
525 | case 'a': |
526 | case 'r': |
527 | case 'g': |
528 | case 'p': |
529 | case 'd': |
530 | case 'l': |
531 | case 'w': |
532 | case 'f': |
533 | case 'e': |
534 | case 's': |
535 | case 'y': |
536 | case 'c': |
537 | if (output_param) { |
538 | output_param = -1; |
539 | cont = 0; |
540 | break; |
541 | } |
542 | output_param = ret; |
543 | break; |
544 | case 'm': |
545 | if (human) { |
546 | output_param = -1; |
547 | cont = 0; |
548 | break; |
549 | } |
550 | human = 1; |
551 | break; |
552 | case 'n': |
553 | no_rounding = 1; |
554 | break; |
555 | default: |
556 | fprintf(stderr, "invalid or unknown argument\n" ); |
557 | return EXIT_FAILURE; |
558 | } |
559 | } while (cont); |
560 | |
561 | switch (output_param) { |
562 | case 'o': |
563 | if (!bitmask_isallclear(bmp: cpus_chosen)) { |
564 | printf(_("The argument passed to this tool can't be " |
565 | "combined with passing a --cpu argument\n" )); |
566 | return -EINVAL; |
567 | } |
568 | break; |
569 | case 0: |
570 | output_param = 'e'; |
571 | } |
572 | |
573 | ret = 0; |
574 | |
575 | /* Default is: show output of base_cpu only */ |
576 | if (bitmask_isallclear(bmp: cpus_chosen)) |
577 | bitmask_setbit(bmp: cpus_chosen, i: base_cpu); |
578 | |
579 | switch (output_param) { |
580 | case -1: |
581 | printf(_("You can't specify more than one --cpu parameter and/or\n" |
582 | "more than one output-specific argument\n" )); |
583 | return -EINVAL; |
584 | case '?': |
585 | printf(_("invalid or unknown argument\n" )); |
586 | return -EINVAL; |
587 | case 'o': |
588 | proc_cpufreq_output(); |
589 | return EXIT_SUCCESS; |
590 | } |
591 | |
592 | for (cpu = bitmask_first(bmp: cpus_chosen); |
593 | cpu <= bitmask_last(bmp: cpus_chosen); cpu++) { |
594 | |
595 | if (!bitmask_isbitset(bmp: cpus_chosen, i: cpu)) |
596 | continue; |
597 | |
598 | printf(_("analyzing CPU %d:\n" ), cpu); |
599 | |
600 | if (sysfs_is_cpu_online(cpu) != 1) { |
601 | printf(_(" *is offline\n" )); |
602 | printf("\n" ); |
603 | continue; |
604 | } |
605 | |
606 | switch (output_param) { |
607 | case 'b': |
608 | get_boost_mode(cpu); |
609 | break; |
610 | case 'e': |
611 | debug_output_one(cpu); |
612 | break; |
613 | case 'a': |
614 | ret = get_affected_cpus(cpu); |
615 | break; |
616 | case 'r': |
617 | ret = get_related_cpus(cpu); |
618 | break; |
619 | case 'g': |
620 | ret = get_available_governors(cpu); |
621 | break; |
622 | case 'p': |
623 | ret = get_policy(cpu); |
624 | break; |
625 | case 'd': |
626 | ret = get_driver(cpu); |
627 | break; |
628 | case 'l': |
629 | ret = get_hardware_limits(cpu, human); |
630 | break; |
631 | case 'w': |
632 | ret = get_freq_hardware(cpu, human); |
633 | break; |
634 | case 'f': |
635 | ret = get_freq_kernel(cpu, human); |
636 | break; |
637 | case 's': |
638 | ret = get_freq_stats(cpu, human); |
639 | break; |
640 | case 'y': |
641 | ret = get_latency(cpu, human); |
642 | break; |
643 | case 'c': |
644 | ret = get_perf_cap(cpu); |
645 | break; |
646 | } |
647 | if (ret) |
648 | return ret; |
649 | } |
650 | return ret; |
651 | } |
652 | |