root/tools/power/cpupower/utils/cpufreq-set.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 <limits.h>
#include <string.h>
#include <ctype.h>

#include <getopt.h>

#include "cpufreq.h"
#include "cpuidle.h"
#include "helpers/helpers.h"

#define NORM_FREQ_LEN 32

static struct option set_opts[] = {
        {"min",         required_argument,      NULL, 'd'},
        {"max",         required_argument,      NULL, 'u'},
        {"governor",    required_argument,      NULL, 'g'},
        {"freq",        required_argument,      NULL, 'f'},
        {"related",     no_argument,            NULL, 'r'},
        { },
};

static void print_error(void)
{
        printf(_("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)
{
        printf(_("invalid or unknown argument\n"));
}

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 {                        /* too 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(_("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 cmd_freq_set(int argc, char **argv)
{
        extern char *optarg;
        extern int optind, opterr, optopt;
        int ret = 0, cont = 1;
        int double_parm = 0, related = 0, policychange = 0;
        unsigned long freq = 0;
        char gov[20];
        unsigned int cpu;

        struct cpufreq_policy new_pol = {
                .min = 0,
                .max = 0,
                .governor = NULL,
        };

        /* parameter parsing */
        do {
                ret = getopt_long(argc, argv, "d:u:g:f:r", set_opts, NULL);
                switch (ret) {
                case '?':
                        print_unknown_arg();
                        return -EINVAL;
                case -1:
                        cont = 0;
                        break;
                case 'r':
                        if (related)
                                double_parm++;
                        related++;
                        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, "%19s", gov)) != 1) {
                                print_unknown_arg();
                                return -EINVAL;
                        }
                        new_pol.governor = gov;
                        break;
                }
        } while (cont);

        /* parameter checking */
        if (double_parm) {
                printf("the same parameter was passed more than once\n");
                return -EINVAL;
        }

        if (freq && policychange) {
                printf(_("the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
                                "-g/--governor parameters\n"));
                return -EINVAL;
        }

        if (!freq && !policychange) {
                printf(_("At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
                                "-g/--governor must be passed\n"));
                return -EINVAL;
        }

        /* Default is: set all CPUs */
        if (bitmask_isallclear(cpus_chosen))
                bitmask_setall(cpus_chosen);

        /* Also set frequency settings for related CPUs if -r is passed */
        if (related) {
                for (cpu = bitmask_first(cpus_chosen);
                     cpu <= bitmask_last(cpus_chosen); cpu++) {
                        struct cpufreq_affected_cpus *cpus;

                        if (!bitmask_isbitset(cpus_chosen, cpu) ||
                            cpupower_is_cpu_online(cpu) != 1)
                                continue;

                        cpus = cpufreq_get_related_cpus(cpu);
                        if (!cpus)
                                break;
                        while (cpus->next) {
                                bitmask_setbit(cpus_chosen, cpus->cpu);
                                cpus = cpus->next;
                        }
                        /* Set the last cpu in related cpus list */
                        bitmask_setbit(cpus_chosen, cpus->cpu);
                        cpufreq_put_related_cpus(cpus);
                }
        }

        get_cpustate();

        /* loop over CPUs */
        for (cpu = bitmask_first(cpus_chosen);
             cpu <= bitmask_last(cpus_chosen); cpu++) {

                if (!bitmask_isbitset(cpus_chosen, cpu) ||
                    cpupower_is_cpu_online(cpu) != 1)
                        continue;

                printf(_("Setting cpu: %d\n"), cpu);
                ret = do_one_cpu(cpu, &new_pol, freq, policychange);
                if (ret) {
                        print_error();
                        return ret;
                }
        }

        print_offline_cpus();

        return 0;
}