18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  (C) 2004-2009  Dominik Brodowski <linux@dominikbrodowski.de>
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <unistd.h>
88c2ecf20Sopenharmony_ci#include <stdio.h>
98c2ecf20Sopenharmony_ci#include <errno.h>
108c2ecf20Sopenharmony_ci#include <stdlib.h>
118c2ecf20Sopenharmony_ci#include <limits.h>
128c2ecf20Sopenharmony_ci#include <string.h>
138c2ecf20Sopenharmony_ci#include <ctype.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <getopt.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include "cpufreq.h"
188c2ecf20Sopenharmony_ci#include "cpuidle.h"
198c2ecf20Sopenharmony_ci#include "helpers/helpers.h"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define NORM_FREQ_LEN 32
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic struct option set_opts[] = {
248c2ecf20Sopenharmony_ci	{"min",		required_argument,	NULL, 'd'},
258c2ecf20Sopenharmony_ci	{"max",		required_argument,	NULL, 'u'},
268c2ecf20Sopenharmony_ci	{"governor",	required_argument,	NULL, 'g'},
278c2ecf20Sopenharmony_ci	{"freq",	required_argument,	NULL, 'f'},
288c2ecf20Sopenharmony_ci	{"related",	no_argument,		NULL, 'r'},
298c2ecf20Sopenharmony_ci	{ },
308c2ecf20Sopenharmony_ci};
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic void print_error(void)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	printf(_("Error setting new values. Common errors:\n"
358c2ecf20Sopenharmony_ci			"- Do you have proper administration rights? (super-user?)\n"
368c2ecf20Sopenharmony_ci			"- Is the governor you requested available and modprobed?\n"
378c2ecf20Sopenharmony_ci			"- Trying to set an invalid policy?\n"
388c2ecf20Sopenharmony_ci			"- Trying to set a specific frequency, but userspace governor is not available,\n"
398c2ecf20Sopenharmony_ci			"   for example because of hardware which cannot be set to a specific frequency\n"
408c2ecf20Sopenharmony_ci			"   or because the userspace governor isn't loaded?\n"));
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistruct freq_units {
448c2ecf20Sopenharmony_ci	char		*str_unit;
458c2ecf20Sopenharmony_ci	int		power_of_ten;
468c2ecf20Sopenharmony_ci};
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ciconst struct freq_units def_units[] = {
498c2ecf20Sopenharmony_ci	{"hz", -3},
508c2ecf20Sopenharmony_ci	{"khz", 0}, /* default */
518c2ecf20Sopenharmony_ci	{"mhz", 3},
528c2ecf20Sopenharmony_ci	{"ghz", 6},
538c2ecf20Sopenharmony_ci	{"thz", 9},
548c2ecf20Sopenharmony_ci	{NULL, 0}
558c2ecf20Sopenharmony_ci};
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_cistatic void print_unknown_arg(void)
588c2ecf20Sopenharmony_ci{
598c2ecf20Sopenharmony_ci	printf(_("invalid or unknown argument\n"));
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic unsigned long string_to_frequency(const char *str)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	char normalized[NORM_FREQ_LEN];
658c2ecf20Sopenharmony_ci	const struct freq_units *unit;
668c2ecf20Sopenharmony_ci	const char *scan;
678c2ecf20Sopenharmony_ci	char *end;
688c2ecf20Sopenharmony_ci	unsigned long freq;
698c2ecf20Sopenharmony_ci	int power = 0, match_count = 0, i, cp, pad;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	while (*str == '0')
728c2ecf20Sopenharmony_ci		str++;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	for (scan = str; isdigit(*scan) || *scan == '.'; scan++) {
758c2ecf20Sopenharmony_ci		if (*scan == '.' && match_count == 0)
768c2ecf20Sopenharmony_ci			match_count = 1;
778c2ecf20Sopenharmony_ci		else if (*scan == '.' && match_count == 1)
788c2ecf20Sopenharmony_ci			return 0;
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (*scan) {
828c2ecf20Sopenharmony_ci		match_count = 0;
838c2ecf20Sopenharmony_ci		for (unit = def_units; unit->str_unit; unit++) {
848c2ecf20Sopenharmony_ci			for (i = 0;
858c2ecf20Sopenharmony_ci			     scan[i] && tolower(scan[i]) == unit->str_unit[i];
868c2ecf20Sopenharmony_ci			     ++i)
878c2ecf20Sopenharmony_ci				continue;
888c2ecf20Sopenharmony_ci			if (scan[i])
898c2ecf20Sopenharmony_ci				continue;
908c2ecf20Sopenharmony_ci			match_count++;
918c2ecf20Sopenharmony_ci			power = unit->power_of_ten;
928c2ecf20Sopenharmony_ci		}
938c2ecf20Sopenharmony_ci		if (match_count != 1)
948c2ecf20Sopenharmony_ci			return 0;
958c2ecf20Sopenharmony_ci	}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	/* count the number of digits to be copied */
988c2ecf20Sopenharmony_ci	for (cp = 0; isdigit(str[cp]); cp++)
998c2ecf20Sopenharmony_ci		continue;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	if (str[cp] == '.') {
1028c2ecf20Sopenharmony_ci		while (power > -1 && isdigit(str[cp+1])) {
1038c2ecf20Sopenharmony_ci			cp++;
1048c2ecf20Sopenharmony_ci			power--;
1058c2ecf20Sopenharmony_ci		}
1068c2ecf20Sopenharmony_ci	}
1078c2ecf20Sopenharmony_ci	if (power >= -1) {		/* not enough => pad */
1088c2ecf20Sopenharmony_ci		pad = power + 1;
1098c2ecf20Sopenharmony_ci	} else {			/* too much => strip */
1108c2ecf20Sopenharmony_ci		pad = 0;
1118c2ecf20Sopenharmony_ci		cp += power + 1;
1128c2ecf20Sopenharmony_ci	}
1138c2ecf20Sopenharmony_ci	/* check bounds */
1148c2ecf20Sopenharmony_ci	if (cp <= 0 || cp + pad > NORM_FREQ_LEN - 1)
1158c2ecf20Sopenharmony_ci		return 0;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	/* copy digits */
1188c2ecf20Sopenharmony_ci	for (i = 0; i < cp; i++, str++) {
1198c2ecf20Sopenharmony_ci		if (*str == '.')
1208c2ecf20Sopenharmony_ci			str++;
1218c2ecf20Sopenharmony_ci		normalized[i] = *str;
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci	/* and pad */
1248c2ecf20Sopenharmony_ci	for (; i < cp + pad; i++)
1258c2ecf20Sopenharmony_ci		normalized[i] = '0';
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	/* round up, down ? */
1288c2ecf20Sopenharmony_ci	match_count = (normalized[i-1] >= '5');
1298c2ecf20Sopenharmony_ci	/* and drop the decimal part */
1308c2ecf20Sopenharmony_ci	normalized[i-1] = 0; /* cp > 0 && pad >= 0 ==> i > 0 */
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	/* final conversion (and applying rounding) */
1338c2ecf20Sopenharmony_ci	errno = 0;
1348c2ecf20Sopenharmony_ci	freq = strtoul(normalized, &end, 10);
1358c2ecf20Sopenharmony_ci	if (errno)
1368c2ecf20Sopenharmony_ci		return 0;
1378c2ecf20Sopenharmony_ci	else {
1388c2ecf20Sopenharmony_ci		if (match_count && freq != ULONG_MAX)
1398c2ecf20Sopenharmony_ci			freq++;
1408c2ecf20Sopenharmony_ci		return freq;
1418c2ecf20Sopenharmony_ci	}
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic int do_new_policy(unsigned int cpu, struct cpufreq_policy *new_pol)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	struct cpufreq_policy *cur_pol = cpufreq_get_policy(cpu);
1478c2ecf20Sopenharmony_ci	int ret;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	if (!cur_pol) {
1508c2ecf20Sopenharmony_ci		printf(_("wrong, unknown or unhandled CPU?\n"));
1518c2ecf20Sopenharmony_ci		return -EINVAL;
1528c2ecf20Sopenharmony_ci	}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	if (!new_pol->min)
1558c2ecf20Sopenharmony_ci		new_pol->min = cur_pol->min;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	if (!new_pol->max)
1588c2ecf20Sopenharmony_ci		new_pol->max = cur_pol->max;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	if (!new_pol->governor)
1618c2ecf20Sopenharmony_ci		new_pol->governor = cur_pol->governor;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	ret = cpufreq_set_policy(cpu, new_pol);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	cpufreq_put_policy(cur_pol);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return ret;
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic int do_one_cpu(unsigned int cpu, struct cpufreq_policy *new_pol,
1728c2ecf20Sopenharmony_ci		unsigned long freq, unsigned int pc)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	switch (pc) {
1758c2ecf20Sopenharmony_ci	case 0:
1768c2ecf20Sopenharmony_ci		return cpufreq_set_frequency(cpu, freq);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	case 1:
1798c2ecf20Sopenharmony_ci		/* if only one value of a policy is to be changed, we can
1808c2ecf20Sopenharmony_ci		 * use a "fast path".
1818c2ecf20Sopenharmony_ci		 */
1828c2ecf20Sopenharmony_ci		if (new_pol->min)
1838c2ecf20Sopenharmony_ci			return cpufreq_modify_policy_min(cpu, new_pol->min);
1848c2ecf20Sopenharmony_ci		else if (new_pol->max)
1858c2ecf20Sopenharmony_ci			return cpufreq_modify_policy_max(cpu, new_pol->max);
1868c2ecf20Sopenharmony_ci		else if (new_pol->governor)
1878c2ecf20Sopenharmony_ci			return cpufreq_modify_policy_governor(cpu,
1888c2ecf20Sopenharmony_ci							new_pol->governor);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	default:
1918c2ecf20Sopenharmony_ci		/* slow path */
1928c2ecf20Sopenharmony_ci		return do_new_policy(cpu, new_pol);
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ciint cmd_freq_set(int argc, char **argv)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	extern char *optarg;
1998c2ecf20Sopenharmony_ci	extern int optind, opterr, optopt;
2008c2ecf20Sopenharmony_ci	int ret = 0, cont = 1;
2018c2ecf20Sopenharmony_ci	int double_parm = 0, related = 0, policychange = 0;
2028c2ecf20Sopenharmony_ci	unsigned long freq = 0;
2038c2ecf20Sopenharmony_ci	char gov[20];
2048c2ecf20Sopenharmony_ci	unsigned int cpu;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	struct cpufreq_policy new_pol = {
2078c2ecf20Sopenharmony_ci		.min = 0,
2088c2ecf20Sopenharmony_ci		.max = 0,
2098c2ecf20Sopenharmony_ci		.governor = NULL,
2108c2ecf20Sopenharmony_ci	};
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/* parameter parsing */
2138c2ecf20Sopenharmony_ci	do {
2148c2ecf20Sopenharmony_ci		ret = getopt_long(argc, argv, "d:u:g:f:r", set_opts, NULL);
2158c2ecf20Sopenharmony_ci		switch (ret) {
2168c2ecf20Sopenharmony_ci		case '?':
2178c2ecf20Sopenharmony_ci			print_unknown_arg();
2188c2ecf20Sopenharmony_ci			return -EINVAL;
2198c2ecf20Sopenharmony_ci		case -1:
2208c2ecf20Sopenharmony_ci			cont = 0;
2218c2ecf20Sopenharmony_ci			break;
2228c2ecf20Sopenharmony_ci		case 'r':
2238c2ecf20Sopenharmony_ci			if (related)
2248c2ecf20Sopenharmony_ci				double_parm++;
2258c2ecf20Sopenharmony_ci			related++;
2268c2ecf20Sopenharmony_ci			break;
2278c2ecf20Sopenharmony_ci		case 'd':
2288c2ecf20Sopenharmony_ci			if (new_pol.min)
2298c2ecf20Sopenharmony_ci				double_parm++;
2308c2ecf20Sopenharmony_ci			policychange++;
2318c2ecf20Sopenharmony_ci			new_pol.min = string_to_frequency(optarg);
2328c2ecf20Sopenharmony_ci			if (new_pol.min == 0) {
2338c2ecf20Sopenharmony_ci				print_unknown_arg();
2348c2ecf20Sopenharmony_ci				return -EINVAL;
2358c2ecf20Sopenharmony_ci			}
2368c2ecf20Sopenharmony_ci			break;
2378c2ecf20Sopenharmony_ci		case 'u':
2388c2ecf20Sopenharmony_ci			if (new_pol.max)
2398c2ecf20Sopenharmony_ci				double_parm++;
2408c2ecf20Sopenharmony_ci			policychange++;
2418c2ecf20Sopenharmony_ci			new_pol.max = string_to_frequency(optarg);
2428c2ecf20Sopenharmony_ci			if (new_pol.max == 0) {
2438c2ecf20Sopenharmony_ci				print_unknown_arg();
2448c2ecf20Sopenharmony_ci				return -EINVAL;
2458c2ecf20Sopenharmony_ci			}
2468c2ecf20Sopenharmony_ci			break;
2478c2ecf20Sopenharmony_ci		case 'f':
2488c2ecf20Sopenharmony_ci			if (freq)
2498c2ecf20Sopenharmony_ci				double_parm++;
2508c2ecf20Sopenharmony_ci			freq = string_to_frequency(optarg);
2518c2ecf20Sopenharmony_ci			if (freq == 0) {
2528c2ecf20Sopenharmony_ci				print_unknown_arg();
2538c2ecf20Sopenharmony_ci				return -EINVAL;
2548c2ecf20Sopenharmony_ci			}
2558c2ecf20Sopenharmony_ci			break;
2568c2ecf20Sopenharmony_ci		case 'g':
2578c2ecf20Sopenharmony_ci			if (new_pol.governor)
2588c2ecf20Sopenharmony_ci				double_parm++;
2598c2ecf20Sopenharmony_ci			policychange++;
2608c2ecf20Sopenharmony_ci			if ((strlen(optarg) < 3) || (strlen(optarg) > 18)) {
2618c2ecf20Sopenharmony_ci				print_unknown_arg();
2628c2ecf20Sopenharmony_ci				return -EINVAL;
2638c2ecf20Sopenharmony_ci			}
2648c2ecf20Sopenharmony_ci			if ((sscanf(optarg, "%19s", gov)) != 1) {
2658c2ecf20Sopenharmony_ci				print_unknown_arg();
2668c2ecf20Sopenharmony_ci				return -EINVAL;
2678c2ecf20Sopenharmony_ci			}
2688c2ecf20Sopenharmony_ci			new_pol.governor = gov;
2698c2ecf20Sopenharmony_ci			break;
2708c2ecf20Sopenharmony_ci		}
2718c2ecf20Sopenharmony_ci	} while (cont);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	/* parameter checking */
2748c2ecf20Sopenharmony_ci	if (double_parm) {
2758c2ecf20Sopenharmony_ci		printf("the same parameter was passed more than once\n");
2768c2ecf20Sopenharmony_ci		return -EINVAL;
2778c2ecf20Sopenharmony_ci	}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	if (freq && policychange) {
2808c2ecf20Sopenharmony_ci		printf(_("the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
2818c2ecf20Sopenharmony_ci				"-g/--governor parameters\n"));
2828c2ecf20Sopenharmony_ci		return -EINVAL;
2838c2ecf20Sopenharmony_ci	}
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	if (!freq && !policychange) {
2868c2ecf20Sopenharmony_ci		printf(_("At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
2878c2ecf20Sopenharmony_ci				"-g/--governor must be passed\n"));
2888c2ecf20Sopenharmony_ci		return -EINVAL;
2898c2ecf20Sopenharmony_ci	}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	/* Default is: set all CPUs */
2928c2ecf20Sopenharmony_ci	if (bitmask_isallclear(cpus_chosen))
2938c2ecf20Sopenharmony_ci		bitmask_setall(cpus_chosen);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	/* Also set frequency settings for related CPUs if -r is passed */
2968c2ecf20Sopenharmony_ci	if (related) {
2978c2ecf20Sopenharmony_ci		for (cpu = bitmask_first(cpus_chosen);
2988c2ecf20Sopenharmony_ci		     cpu <= bitmask_last(cpus_chosen); cpu++) {
2998c2ecf20Sopenharmony_ci			struct cpufreq_affected_cpus *cpus;
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci			if (!bitmask_isbitset(cpus_chosen, cpu) ||
3028c2ecf20Sopenharmony_ci			    cpupower_is_cpu_online(cpu) != 1)
3038c2ecf20Sopenharmony_ci				continue;
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci			cpus = cpufreq_get_related_cpus(cpu);
3068c2ecf20Sopenharmony_ci			if (!cpus)
3078c2ecf20Sopenharmony_ci				break;
3088c2ecf20Sopenharmony_ci			while (cpus->next) {
3098c2ecf20Sopenharmony_ci				bitmask_setbit(cpus_chosen, cpus->cpu);
3108c2ecf20Sopenharmony_ci				cpus = cpus->next;
3118c2ecf20Sopenharmony_ci			}
3128c2ecf20Sopenharmony_ci			/* Set the last cpu in related cpus list */
3138c2ecf20Sopenharmony_ci			bitmask_setbit(cpus_chosen, cpus->cpu);
3148c2ecf20Sopenharmony_ci			cpufreq_put_related_cpus(cpus);
3158c2ecf20Sopenharmony_ci		}
3168c2ecf20Sopenharmony_ci	}
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	/* loop over CPUs */
3208c2ecf20Sopenharmony_ci	for (cpu = bitmask_first(cpus_chosen);
3218c2ecf20Sopenharmony_ci	     cpu <= bitmask_last(cpus_chosen); cpu++) {
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci		if (!bitmask_isbitset(cpus_chosen, cpu) ||
3248c2ecf20Sopenharmony_ci		    cpupower_is_cpu_online(cpu) != 1)
3258c2ecf20Sopenharmony_ci			continue;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci		printf(_("Setting cpu: %d\n"), cpu);
3288c2ecf20Sopenharmony_ci		ret = do_one_cpu(cpu, &new_pol, freq, policychange);
3298c2ecf20Sopenharmony_ci		if (ret) {
3308c2ecf20Sopenharmony_ci			print_error();
3318c2ecf20Sopenharmony_ci			return ret;
3328c2ecf20Sopenharmony_ci		}
3338c2ecf20Sopenharmony_ci	}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	return 0;
3368c2ecf20Sopenharmony_ci}
337