root/tools/power/cpupower/utils/cpufreq-info.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  (C) 2004-2009  Dominik Brodowski <linux@dominikbrodowski.de>
 */


#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include <getopt.h>

#include "cpufreq.h"
#include "helpers/sysfs.h"
#include "helpers/helpers.h"
#include "helpers/bitmask.h"

#define LINE_LEN 10

static unsigned int count_cpus(void)
{
        FILE *fp;
        char value[LINE_LEN];
        unsigned int ret = 0;
        unsigned int cpunr = 0;

        fp = fopen("/proc/stat", "r");
        if (!fp) {
                printf(_("Couldn't count the number of CPUs (%s: %s), assuming 1\n"), "/proc/stat", strerror(errno));
                return 1;
        }

        while (!feof(fp)) {
                if (!fgets(value, LINE_LEN, fp))
                        continue;
                value[LINE_LEN - 1] = '\0';
                if (strlen(value) < (LINE_LEN - 2))
                        continue;
                if (strstr(value, "cpu "))
                        continue;
                if (sscanf(value, "cpu%d ", &cpunr) != 1)
                        continue;
                if (cpunr > ret)
                        ret = cpunr;
        }
        fclose(fp);

        /* cpu count starts from 0, on error return 1 (UP) */
        return ret + 1;
}


static void proc_cpufreq_output(void)
{
        unsigned int cpu, nr_cpus;
        struct cpufreq_policy *policy;
        unsigned int min_pctg = 0;
        unsigned int max_pctg = 0;
        unsigned long min, max;

        printf(_("          minimum CPU frequency  -  maximum CPU frequency  -  governor\n"));

        nr_cpus = count_cpus();
        for (cpu = 0; cpu < nr_cpus; cpu++) {
                policy = cpufreq_get_policy(cpu);
                if (!policy)
                        continue;

                if (cpufreq_get_hardware_limits(cpu, &min, &max)) {
                        max = 0;
                } else {
                        min_pctg = (policy->min * 100) / max;
                        max_pctg = (policy->max * 100) / max;
                }
                printf("CPU%3d    %9lu kHz (%3d %%)  -  %9lu kHz (%3d %%)  -  %s\n",
                        cpu , policy->min, max ? min_pctg : 0, policy->max,
                        max ? max_pctg : 0, policy->governor);

                cpufreq_put_policy(policy);
        }
}

static int no_rounding;
static void print_duration(unsigned long duration)
{
        unsigned long tmp;

        if (no_rounding) {
                if (duration > 1000000)
                        printf("%u.%06u ms", ((unsigned int) duration/1000000),
                                ((unsigned int) duration%1000000));
                else if (duration > 100000)
                        printf("%u us", ((unsigned int) duration/1000));
                else if (duration > 1000)
                        printf("%u.%03u us", ((unsigned int) duration/1000),
                                ((unsigned int) duration%1000));
                else
                        printf("%lu ns", duration);
        } else {
                if (duration > 1000000) {
                        tmp = duration%10000;
                        if (tmp >= 5000)
                                duration += 10000;
                        printf("%u.%02u ms", ((unsigned int) duration/1000000),
                                ((unsigned int) (duration%1000000)/10000));
                } else if (duration > 100000) {
                        tmp = duration%1000;
                        if (tmp >= 500)
                                duration += 1000;
                        printf("%u us", ((unsigned int) duration / 1000));
                } else if (duration > 1000) {
                        tmp = duration%100;
                        if (tmp >= 50)
                                duration += 100;
                        printf("%u.%01u us", ((unsigned int) duration/1000),
                                ((unsigned int) (duration%1000)/100));
                } else
                        printf("%lu ns", duration);
        }
}

static int get_boost_mode_x86(unsigned int cpu)
{
        int support, active, b_states = 0, ret, pstate_no, i;
        /* ToDo: Make this more global */
        unsigned long pstates[MAX_HW_PSTATES] = {0,};

        ret = cpufreq_has_x86_boost_support(cpu, &support, &active, &b_states);
        if (ret) {
                printf(_("Error while evaluating Boost Capabilities"
                                " on CPU %d -- are you root?\n"), cpu);
                return ret;
        }
        /* P state changes via MSR are identified via cpuid 80000007
           on Intel and AMD, but we assume boost capable machines can do that
           if (cpuid_eax(0x80000000) >= 0x80000007
           && (cpuid_edx(0x80000007) & (1 << 7)))
        */

        printf(_("  boost state support:\n"));

        printf(_("    Supported: %s\n"), support ? _("yes") : _("no"));
        printf(_("    Active: %s\n"), active ? _("yes") : _("no"));

        if (cpupower_cpu_info.vendor == X86_VENDOR_AMD &&
            cpupower_cpu_info.caps & CPUPOWER_CAP_AMD_PSTATE) {
                return 0;
        } else if ((cpupower_cpu_info.vendor == X86_VENDOR_AMD &&
                    cpupower_cpu_info.family >= 0x10) ||
                   cpupower_cpu_info.vendor == X86_VENDOR_HYGON) {
                ret = decode_pstates(cpu, b_states, pstates, &pstate_no);
                if (ret)
                        return ret;

                printf(_("    Boost States: %d\n"), b_states);
                printf(_("    Total States: %d\n"), pstate_no);
                for (i = 0; i < pstate_no; i++) {
                        if (!pstates[i])
                                continue;
                        if (i < b_states)
                                printf(_("    Pstate-Pb%d: %luMHz (boost state)"
                                         "\n"), i, pstates[i]);
                        else
                                printf(_("    Pstate-P%d:  %luMHz\n"),
                                       i - b_states, pstates[i]);
                }
        } else if (cpupower_cpu_info.caps & CPUPOWER_CAP_HAS_TURBO_RATIO) {
                double bclk;
                unsigned long long intel_turbo_ratio = 0;
                unsigned int ratio;

                /* Any way to autodetect this ? */
                if (cpupower_cpu_info.caps & CPUPOWER_CAP_IS_SNB)
                        bclk = 100.00;
                else
                        bclk = 133.33;
                intel_turbo_ratio = msr_intel_get_turbo_ratio(cpu);
                dprint ("    Ratio: 0x%llx - bclk: %f\n",
                        intel_turbo_ratio, bclk);

                ratio = (intel_turbo_ratio >> 24) & 0xFF;
                if (ratio)
                        printf(_("    %.0f MHz max turbo 4 active cores\n"),
                               ratio * bclk);

                ratio = (intel_turbo_ratio >> 16) & 0xFF;
                if (ratio)
                        printf(_("    %.0f MHz max turbo 3 active cores\n"),
                               ratio * bclk);

                ratio = (intel_turbo_ratio >> 8) & 0xFF;
                if (ratio)
                        printf(_("    %.0f MHz max turbo 2 active cores\n"),
                               ratio * bclk);

                ratio = (intel_turbo_ratio >> 0) & 0xFF;
                if (ratio)
                        printf(_("    %.0f MHz max turbo 1 active cores\n"),
                               ratio * bclk);
        }
        return 0;
}

static int get_boost_mode_generic(unsigned int cpu)
{
        bool active;

        if (!cpufreq_has_generic_boost_support(&active)) {
                printf(_("  boost state support:\n"));
                printf(_("    Active: %s\n"), active ? _("yes") : _("no"));
        }

        return 0;
}

/* --boost / -b */

static int get_boost_mode(unsigned int cpu)
{
        struct cpufreq_available_frequencies *freqs;

        if (cpupower_cpu_info.vendor == X86_VENDOR_AMD ||
            cpupower_cpu_info.vendor == X86_VENDOR_HYGON ||
            cpupower_cpu_info.vendor == X86_VENDOR_INTEL)
                return get_boost_mode_x86(cpu);
        else
                get_boost_mode_generic(cpu);

        freqs = cpufreq_get_boost_frequencies(cpu);
        if (freqs) {
                printf(_("  boost frequency steps: "));
                while (freqs->next) {
                        print_speed(freqs->frequency, no_rounding);
                        printf(", ");
                        freqs = freqs->next;
                }
                print_speed(freqs->frequency, no_rounding);
                printf("\n");
                cpufreq_put_available_frequencies(freqs);
        }

        return 0;
}

/* --freq / -f */

static int get_freq_kernel(unsigned int cpu, unsigned int human)
{
        unsigned long freq = cpufreq_get_freq_kernel(cpu);
        printf(_("  current CPU frequency: "));
        if (!freq) {
                printf(_(" Unable to call to kernel\n"));
                return -EINVAL;
        }
        if (human) {
                print_speed(freq, no_rounding);
        } else
                printf("%lu", freq);
        printf(_(" (asserted by call to kernel)\n"));
        return 0;
}


/* --hwfreq / -w */

static int get_freq_hardware(unsigned int cpu, unsigned int human)
{
        unsigned long freq;

        if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF))
                return -EINVAL;

        freq = cpufreq_get_freq_hardware(cpu);
        printf(_("  current CPU frequency: "));
        if (!freq) {
                printf("Unable to call hardware\n");
                return -EINVAL;
        }
        if (human) {
                print_speed(freq, no_rounding);
        } else
                printf("%lu", freq);
        printf(_(" (asserted by call to hardware)\n"));
        return 0;
}

/* --hwlimits / -l */

static int get_hardware_limits(unsigned int cpu, unsigned int human)
{
        unsigned long min, max;

        if (cpufreq_get_hardware_limits(cpu, &min, &max)) {
                printf(_("Not Available\n"));
                return -EINVAL;
        }

        if (human) {
                printf(_("  hardware limits: "));
                print_speed(min, no_rounding);
                printf(" - ");
                print_speed(max, no_rounding);
                printf("\n");
        } else {
                printf("%lu %lu\n", min, max);
        }
        return 0;
}

/* --driver / -d */

static int get_driver(unsigned int cpu)
{
        char *driver = cpufreq_get_driver(cpu);
        if (!driver) {
                printf(_("  no or unknown cpufreq driver is active on this CPU\n"));
                return -EINVAL;
        }
        printf("  driver: %s\n", driver);
        cpufreq_put_driver(driver);
        return 0;
}

/* --policy / -p */

static int get_policy(unsigned int cpu)
{
        struct cpufreq_policy *policy = cpufreq_get_policy(cpu);
        if (!policy) {
                printf(_("  Unable to determine current policy\n"));
                return -EINVAL;
        }
        printf(_("  current policy: frequency should be within "));
        print_speed(policy->min, no_rounding);
        printf(_(" and "));
        print_speed(policy->max, no_rounding);

        printf(".\n                  ");
        printf(_("The governor \"%s\" may decide which speed to use\n"
               "                  within this range.\n"),
               policy->governor);
        cpufreq_put_policy(policy);
        return 0;
}

/* --governors / -g */

static int get_available_governors(unsigned int cpu)
{
        struct cpufreq_available_governors *governors =
                cpufreq_get_available_governors(cpu);

        printf(_("  available cpufreq governors: "));
        if (!governors) {
                printf(_("Not Available\n"));
                return -EINVAL;
        }

        while (governors->next) {
                printf("%s ", governors->governor);
                governors = governors->next;
        }
        printf("%s\n", governors->governor);
        cpufreq_put_available_governors(governors);
        return 0;
}


/* --affected-cpus  / -a */

static int get_affected_cpus(unsigned int cpu)
{
        struct cpufreq_affected_cpus *cpus = cpufreq_get_affected_cpus(cpu);

        printf(_("  CPUs which need to have their frequency coordinated by software: "));
        if (!cpus) {
                printf(_("Not Available\n"));
                return -EINVAL;
        }

        while (cpus->next) {
                printf("%d ", cpus->cpu);
                cpus = cpus->next;
        }
        printf("%d\n", cpus->cpu);
        cpufreq_put_affected_cpus(cpus);
        return 0;
}

/* --related-cpus  / -r */

static int get_related_cpus(unsigned int cpu)
{
        struct cpufreq_affected_cpus *cpus = cpufreq_get_related_cpus(cpu);

        printf(_("  CPUs which run at the same hardware frequency: "));
        if (!cpus) {
                printf(_("Not Available\n"));
                return -EINVAL;
        }

        while (cpus->next) {
                printf("%d ", cpus->cpu);
                cpus = cpus->next;
        }
        printf("%d\n", cpus->cpu);
        cpufreq_put_related_cpus(cpus);
        return 0;
}

/* --stats / -s */

static int get_freq_stats(unsigned int cpu, unsigned int human)
{
        unsigned long total_trans = cpufreq_get_transitions(cpu);
        unsigned long long total_time;
        struct cpufreq_stats *stats = cpufreq_get_stats(cpu, &total_time);
        while (stats) {
                if (human) {
                        print_speed(stats->frequency, no_rounding);
                        printf(":%.2f%%",
                                (100.0 * stats->time_in_state) / total_time);
                } else
                        printf("%lu:%llu",
                                stats->frequency, stats->time_in_state);
                stats = stats->next;
                if (stats)
                        printf(", ");
        }
        cpufreq_put_stats(stats);
        if (total_trans)
                printf("  (%lu)\n", total_trans);
        return 0;
}

/* --epp / -z */

static int get_epp(unsigned int cpu, bool interactive)
{
        char *epp;

        epp = cpufreq_get_energy_performance_preference(cpu);
        if (!epp)
                return -EINVAL;
        if (interactive)
                printf(_("  energy performance preference: %s\n"), epp);

        cpufreq_put_energy_performance_preference(epp);

        return 0;
}

/* --latency / -y */

static int get_latency(unsigned int cpu, unsigned int human)
{
        unsigned long latency = cpufreq_get_transition_latency(cpu);

        if (!get_epp(cpu, false))
                return -EINVAL;

        printf(_("  maximum transition latency: "));
        if (!latency || latency == UINT_MAX) {
                printf(_(" Cannot determine or is not supported.\n"));
                return -EINVAL;
        }

        if (human) {
                print_duration(latency);
                printf("\n");
        } else
                printf("%lu\n", latency);
        return 0;
}

/* --performance / -c */

static int get_perf_cap(unsigned int cpu)
{
        if (cpupower_cpu_info.vendor == X86_VENDOR_AMD &&
            cpupower_cpu_info.caps & CPUPOWER_CAP_AMD_PSTATE)
                amd_pstate_show_perf_and_freq(cpu, no_rounding);

        return 0;
}

static void debug_output_one(unsigned int cpu)
{
        struct cpufreq_available_frequencies *freqs;

        get_driver(cpu);
        get_related_cpus(cpu);
        get_affected_cpus(cpu);
        get_latency(cpu, 1);
        get_epp(cpu, true);
        get_hardware_limits(cpu, 1);

        freqs = cpufreq_get_available_frequencies(cpu);
        if (freqs) {
                printf(_("  available frequency steps:  "));
                while (freqs->next) {
                        print_speed(freqs->frequency, no_rounding);
                        printf(", ");
                        freqs = freqs->next;
                }
                print_speed(freqs->frequency, no_rounding);
                printf("\n");
                cpufreq_put_available_frequencies(freqs);
        }

        get_available_governors(cpu);
        get_policy(cpu);
        if (get_freq_hardware(cpu, 1) < 0)
                get_freq_kernel(cpu, 1);
        get_boost_mode(cpu);
        get_perf_cap(cpu);
}

static struct option info_opts[] = {
        {"debug",        no_argument,            NULL,   'e'},
        {"boost",        no_argument,            NULL,   'b'},
        {"freq",         no_argument,            NULL,   'f'},
        {"hwfreq",       no_argument,            NULL,   'w'},
        {"hwlimits",     no_argument,            NULL,   'l'},
        {"driver",       no_argument,            NULL,   'd'},
        {"policy",       no_argument,            NULL,   'p'},
        {"governors",    no_argument,            NULL,   'g'},
        {"related-cpus",  no_argument,   NULL,   'r'},
        {"affected-cpus", no_argument,   NULL,   'a'},
        {"stats",        no_argument,            NULL,   's'},
        {"latency",      no_argument,            NULL,   'y'},
        {"proc",         no_argument,            NULL,   'o'},
        {"human",        no_argument,            NULL,   'm'},
        {"no-rounding", no_argument,     NULL,   'n'},
        {"performance", no_argument,     NULL,   'c'},
        {"epp",          no_argument,            NULL,   'z'},
        { },
};

int cmd_freq_info(int argc, char **argv)
{
        int ret = 0, cont = 1;
        unsigned int cpu = 0;
        unsigned int human = 0;
        int output_param = 0;

        do {
                ret = getopt_long(argc, argv, "oefwldpgrasmybncz", info_opts,
                                  NULL);
                switch (ret) {
                case '?':
                        output_param = '?';
                        cont = 0;
                        break;
                case -1:
                        cont = 0;
                        break;
                case 'b':
                case 'o':
                case 'a':
                case 'r':
                case 'g':
                case 'p':
                case 'd':
                case 'l':
                case 'w':
                case 'f':
                case 'e':
                case 's':
                case 'y':
                case 'c':
                case 'z':
                        if (output_param) {
                                output_param = -1;
                                cont = 0;
                                break;
                        }
                        output_param = ret;
                        break;
                case 'm':
                        if (human) {
                                output_param = -1;
                                cont = 0;
                                break;
                        }
                        human = 1;
                        break;
                case 'n':
                        no_rounding = 1;
                        break;
                default:
                        fprintf(stderr, "invalid or unknown argument\n");
                        return EXIT_FAILURE;
                }
        } while (cont);

        switch (output_param) {
        case 'o':
                if (!bitmask_isallclear(cpus_chosen)) {
                        printf(_("The argument passed to this tool can't be "
                                 "combined with passing a --cpu argument\n"));
                        return -EINVAL;
                }
                break;
        case 0:
                output_param = 'e';
        }

        ret = 0;

        /* Default is: show output of base_cpu only */
        if (bitmask_isallclear(cpus_chosen))
                bitmask_setbit(cpus_chosen, base_cpu);

        switch (output_param) {
        case -1:
                printf(_("You can't specify more than one --cpu parameter and/or\n"
                       "more than one output-specific argument\n"));
                return -EINVAL;
        case '?':
                printf(_("invalid or unknown argument\n"));
                return -EINVAL;
        case 'o':
                proc_cpufreq_output();
                return EXIT_SUCCESS;
        }

        for (cpu = bitmask_first(cpus_chosen);
             cpu <= bitmask_last(cpus_chosen); cpu++) {

                if (!bitmask_isbitset(cpus_chosen, cpu))
                        continue;

                printf(_("analyzing CPU %d:\n"), cpu);

                if (sysfs_is_cpu_online(cpu) != 1) {
                        printf(_(" *is offline\n"));
                        printf("\n");
                        continue;
                }

                switch (output_param) {
                case 'b':
                        get_boost_mode(cpu);
                        break;
                case 'e':
                        debug_output_one(cpu);
                        break;
                case 'a':
                        ret = get_affected_cpus(cpu);
                        break;
                case 'r':
                        ret = get_related_cpus(cpu);
                        break;
                case 'g':
                        ret = get_available_governors(cpu);
                        break;
                case 'p':
                        ret = get_policy(cpu);
                        break;
                case 'd':
                        ret = get_driver(cpu);
                        break;
                case 'l':
                        ret = get_hardware_limits(cpu, human);
                        break;
                case 'w':
                        ret = get_freq_hardware(cpu, human);
                        break;
                case 'f':
                        ret = get_freq_kernel(cpu, human);
                        break;
                case 's':
                        ret = get_freq_stats(cpu, human);
                        break;
                case 'y':
                        ret = get_latency(cpu, human);
                        break;
                case 'c':
                        ret = get_perf_cap(cpu);
                        break;
                case 'z':
                        ret = get_epp(cpu, true);
                        break;
                }
                if (ret)
                        return ret;
        }
        return ret;
}