1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 *  (C) 2004-2009  Dominik Brodowski <linux@dominikbrodowski.de>
4 */
5
6
7#include <stdio.h>
8#include <errno.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/types.h>
12#include <sys/stat.h>
13#include <fcntl.h>
14#include <unistd.h>
15
16#include "cpufreq.h"
17#include "cpupower_intern.h"
18
19/* CPUFREQ sysfs access **************************************************/
20
21/* helper function to read file from /sys into given buffer */
22/* fname is a relative path under "cpuX/cpufreq" dir */
23static unsigned int sysfs_cpufreq_read_file(unsigned int cpu, const char *fname,
24					    char *buf, size_t buflen)
25{
26	char path[SYSFS_PATH_MAX];
27
28	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpufreq/%s",
29			 cpu, fname);
30	return cpupower_read_sysfs(path, buf, buflen);
31}
32
33/* helper function to write a new value to a /sys file */
34/* fname is a relative path under "cpuX/cpufreq" dir */
35static unsigned int sysfs_cpufreq_write_file(unsigned int cpu,
36					     const char *fname,
37					     const char *value, size_t len)
38{
39	char path[SYSFS_PATH_MAX];
40	int fd;
41	ssize_t numwrite;
42
43	snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpufreq/%s",
44			 cpu, fname);
45
46	fd = open(path, O_WRONLY);
47	if (fd == -1)
48		return 0;
49
50	numwrite = write(fd, value, len);
51	if (numwrite < 1) {
52		close(fd);
53		return 0;
54	}
55
56	close(fd);
57
58	return (unsigned int) numwrite;
59}
60
61/* read access to files which contain one numeric value */
62
63enum cpufreq_value {
64	CPUINFO_CUR_FREQ,
65	CPUINFO_MIN_FREQ,
66	CPUINFO_MAX_FREQ,
67	CPUINFO_LATENCY,
68	SCALING_CUR_FREQ,
69	SCALING_MIN_FREQ,
70	SCALING_MAX_FREQ,
71	STATS_NUM_TRANSITIONS,
72	MAX_CPUFREQ_VALUE_READ_FILES
73};
74
75static const char *cpufreq_value_files[MAX_CPUFREQ_VALUE_READ_FILES] = {
76	[CPUINFO_CUR_FREQ] = "cpuinfo_cur_freq",
77	[CPUINFO_MIN_FREQ] = "cpuinfo_min_freq",
78	[CPUINFO_MAX_FREQ] = "cpuinfo_max_freq",
79	[CPUINFO_LATENCY]  = "cpuinfo_transition_latency",
80	[SCALING_CUR_FREQ] = "scaling_cur_freq",
81	[SCALING_MIN_FREQ] = "scaling_min_freq",
82	[SCALING_MAX_FREQ] = "scaling_max_freq",
83	[STATS_NUM_TRANSITIONS] = "stats/total_trans"
84};
85
86
87static unsigned long sysfs_cpufreq_get_one_value(unsigned int cpu,
88						 enum cpufreq_value which)
89{
90	unsigned long value;
91	unsigned int len;
92	char linebuf[MAX_LINE_LEN];
93	char *endp;
94
95	if (which >= MAX_CPUFREQ_VALUE_READ_FILES)
96		return 0;
97
98	len = sysfs_cpufreq_read_file(cpu, cpufreq_value_files[which],
99				linebuf, sizeof(linebuf));
100
101	if (len == 0)
102		return 0;
103
104	value = strtoul(linebuf, &endp, 0);
105
106	if (endp == linebuf || errno == ERANGE)
107		return 0;
108
109	return value;
110}
111
112/* read access to files which contain one string */
113
114enum cpufreq_string {
115	SCALING_DRIVER,
116	SCALING_GOVERNOR,
117	MAX_CPUFREQ_STRING_FILES
118};
119
120static const char *cpufreq_string_files[MAX_CPUFREQ_STRING_FILES] = {
121	[SCALING_DRIVER] = "scaling_driver",
122	[SCALING_GOVERNOR] = "scaling_governor",
123};
124
125
126static char *sysfs_cpufreq_get_one_string(unsigned int cpu,
127					   enum cpufreq_string which)
128{
129	char linebuf[MAX_LINE_LEN];
130	char *result;
131	unsigned int len;
132
133	if (which >= MAX_CPUFREQ_STRING_FILES)
134		return NULL;
135
136	len = sysfs_cpufreq_read_file(cpu, cpufreq_string_files[which],
137				linebuf, sizeof(linebuf));
138	if (len == 0)
139		return NULL;
140
141	result = strdup(linebuf);
142	if (result == NULL)
143		return NULL;
144
145	if (result[strlen(result) - 1] == '\n')
146		result[strlen(result) - 1] = '\0';
147
148	return result;
149}
150
151/* write access */
152
153enum cpufreq_write {
154	WRITE_SCALING_MIN_FREQ,
155	WRITE_SCALING_MAX_FREQ,
156	WRITE_SCALING_GOVERNOR,
157	WRITE_SCALING_SET_SPEED,
158	MAX_CPUFREQ_WRITE_FILES
159};
160
161static const char *cpufreq_write_files[MAX_CPUFREQ_WRITE_FILES] = {
162	[WRITE_SCALING_MIN_FREQ] = "scaling_min_freq",
163	[WRITE_SCALING_MAX_FREQ] = "scaling_max_freq",
164	[WRITE_SCALING_GOVERNOR] = "scaling_governor",
165	[WRITE_SCALING_SET_SPEED] = "scaling_setspeed",
166};
167
168static int sysfs_cpufreq_write_one_value(unsigned int cpu,
169					 enum cpufreq_write which,
170					 const char *new_value, size_t len)
171{
172	if (which >= MAX_CPUFREQ_WRITE_FILES)
173		return 0;
174
175	if (sysfs_cpufreq_write_file(cpu, cpufreq_write_files[which],
176					new_value, len) != len)
177		return -ENODEV;
178
179	return 0;
180};
181
182unsigned long cpufreq_get_freq_kernel(unsigned int cpu)
183{
184	return sysfs_cpufreq_get_one_value(cpu, SCALING_CUR_FREQ);
185}
186
187unsigned long cpufreq_get_freq_hardware(unsigned int cpu)
188{
189	return sysfs_cpufreq_get_one_value(cpu, CPUINFO_CUR_FREQ);
190}
191
192unsigned long cpufreq_get_transition_latency(unsigned int cpu)
193{
194	return sysfs_cpufreq_get_one_value(cpu, CPUINFO_LATENCY);
195}
196
197int cpufreq_get_hardware_limits(unsigned int cpu,
198				unsigned long *min,
199				unsigned long *max)
200{
201	if ((!min) || (!max))
202		return -EINVAL;
203
204	*min = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MIN_FREQ);
205	if (!*min)
206		return -ENODEV;
207
208	*max = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MAX_FREQ);
209	if (!*max)
210		return -ENODEV;
211
212	return 0;
213}
214
215char *cpufreq_get_driver(unsigned int cpu)
216{
217	return sysfs_cpufreq_get_one_string(cpu, SCALING_DRIVER);
218}
219
220void cpufreq_put_driver(char *ptr)
221{
222	if (!ptr)
223		return;
224	free(ptr);
225}
226
227struct cpufreq_policy *cpufreq_get_policy(unsigned int cpu)
228{
229	struct cpufreq_policy *policy;
230
231	policy = malloc(sizeof(struct cpufreq_policy));
232	if (!policy)
233		return NULL;
234
235	policy->governor = sysfs_cpufreq_get_one_string(cpu, SCALING_GOVERNOR);
236	if (!policy->governor) {
237		free(policy);
238		return NULL;
239	}
240	policy->min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ);
241	policy->max = sysfs_cpufreq_get_one_value(cpu, SCALING_MAX_FREQ);
242	if ((!policy->min) || (!policy->max)) {
243		free(policy->governor);
244		free(policy);
245		return NULL;
246	}
247
248	return policy;
249}
250
251void cpufreq_put_policy(struct cpufreq_policy *policy)
252{
253	if ((!policy) || (!policy->governor))
254		return;
255
256	free(policy->governor);
257	policy->governor = NULL;
258	free(policy);
259}
260
261struct cpufreq_available_governors *cpufreq_get_available_governors(unsigned
262								int cpu)
263{
264	struct cpufreq_available_governors *first = NULL;
265	struct cpufreq_available_governors *current = NULL;
266	char linebuf[MAX_LINE_LEN];
267	unsigned int pos, i;
268	unsigned int len;
269
270	len = sysfs_cpufreq_read_file(cpu, "scaling_available_governors",
271				linebuf, sizeof(linebuf));
272	if (len == 0)
273		return NULL;
274
275	pos = 0;
276	for (i = 0; i < len; i++) {
277		if (linebuf[i] == ' ' || linebuf[i] == '\n') {
278			if (i - pos < 2)
279				continue;
280			if (current) {
281				current->next = malloc(sizeof(*current));
282				if (!current->next)
283					goto error_out;
284				current = current->next;
285			} else {
286				first = malloc(sizeof(*first));
287				if (!first)
288					return NULL;
289				current = first;
290			}
291			current->first = first;
292			current->next = NULL;
293
294			current->governor = malloc(i - pos + 1);
295			if (!current->governor)
296				goto error_out;
297
298			memcpy(current->governor, linebuf + pos, i - pos);
299			current->governor[i - pos] = '\0';
300			pos = i + 1;
301		}
302	}
303
304	return first;
305
306 error_out:
307	while (first) {
308		current = first->next;
309		if (first->governor)
310			free(first->governor);
311		free(first);
312		first = current;
313	}
314	return NULL;
315}
316
317void cpufreq_put_available_governors(struct cpufreq_available_governors *any)
318{
319	struct cpufreq_available_governors *tmp, *next;
320
321	if (!any)
322		return;
323
324	tmp = any->first;
325	while (tmp) {
326		next = tmp->next;
327		if (tmp->governor)
328			free(tmp->governor);
329		free(tmp);
330		tmp = next;
331	}
332}
333
334
335struct cpufreq_available_frequencies
336*cpufreq_get_available_frequencies(unsigned int cpu)
337{
338	struct cpufreq_available_frequencies *first = NULL;
339	struct cpufreq_available_frequencies *current = NULL;
340	char one_value[SYSFS_PATH_MAX];
341	char linebuf[MAX_LINE_LEN];
342	unsigned int pos, i;
343	unsigned int len;
344
345	len = sysfs_cpufreq_read_file(cpu, "scaling_available_frequencies",
346				      linebuf, sizeof(linebuf));
347	if (len == 0)
348		return NULL;
349
350	pos = 0;
351	for (i = 0; i < len; i++) {
352		if (linebuf[i] == ' ' || linebuf[i] == '\n') {
353			if (i - pos < 2)
354				continue;
355			if (i - pos >= SYSFS_PATH_MAX)
356				goto error_out;
357			if (current) {
358				current->next = malloc(sizeof(*current));
359				if (!current->next)
360					goto error_out;
361				current = current->next;
362			} else {
363				first = malloc(sizeof(*first));
364				if (!first)
365					return NULL;
366				current = first;
367			}
368			current->first = first;
369			current->next = NULL;
370
371			memcpy(one_value, linebuf + pos, i - pos);
372			one_value[i - pos] = '\0';
373			if (sscanf(one_value, "%lu", &current->frequency) != 1)
374				goto error_out;
375
376			pos = i + 1;
377		}
378	}
379
380	return first;
381
382 error_out:
383	while (first) {
384		current = first->next;
385		free(first);
386		first = current;
387	}
388	return NULL;
389}
390
391struct cpufreq_available_frequencies
392*cpufreq_get_boost_frequencies(unsigned int cpu)
393{
394	struct cpufreq_available_frequencies *first = NULL;
395	struct cpufreq_available_frequencies *current = NULL;
396	char one_value[SYSFS_PATH_MAX];
397	char linebuf[MAX_LINE_LEN];
398	unsigned int pos, i;
399	unsigned int len;
400
401	len = sysfs_cpufreq_read_file(cpu, "scaling_boost_frequencies",
402				      linebuf, sizeof(linebuf));
403	if (len == 0)
404		return NULL;
405
406	pos = 0;
407	for (i = 0; i < len; i++) {
408		if (linebuf[i] == ' ' || linebuf[i] == '\n') {
409			if (i - pos < 2)
410				continue;
411			if (i - pos >= SYSFS_PATH_MAX)
412				goto error_out;
413			if (current) {
414				current->next = malloc(sizeof(*current));
415				if (!current->next)
416					goto error_out;
417				current = current->next;
418			} else {
419				first = malloc(sizeof(*first));
420				if (!first)
421					return NULL;
422				current = first;
423			}
424			current->first = first;
425			current->next = NULL;
426
427			memcpy(one_value, linebuf + pos, i - pos);
428			one_value[i - pos] = '\0';
429			if (sscanf(one_value, "%lu", &current->frequency) != 1)
430				goto error_out;
431
432			pos = i + 1;
433		}
434	}
435
436	return first;
437
438 error_out:
439	while (first) {
440		current = first->next;
441		free(first);
442		first = current;
443	}
444	return NULL;
445}
446
447void cpufreq_put_available_frequencies(struct cpufreq_available_frequencies *any)
448{
449	struct cpufreq_available_frequencies *tmp, *next;
450
451	if (!any)
452		return;
453
454	tmp = any->first;
455	while (tmp) {
456		next = tmp->next;
457		free(tmp);
458		tmp = next;
459	}
460}
461
462void cpufreq_put_boost_frequencies(struct cpufreq_available_frequencies *any)
463{
464	cpufreq_put_available_frequencies(any);
465}
466
467static struct cpufreq_affected_cpus *sysfs_get_cpu_list(unsigned int cpu,
468							const char *file)
469{
470	struct cpufreq_affected_cpus *first = NULL;
471	struct cpufreq_affected_cpus *current = NULL;
472	char one_value[SYSFS_PATH_MAX];
473	char linebuf[MAX_LINE_LEN];
474	unsigned int pos, i;
475	unsigned int len;
476
477	len = sysfs_cpufreq_read_file(cpu, file, linebuf, sizeof(linebuf));
478	if (len == 0)
479		return NULL;
480
481	pos = 0;
482	for (i = 0; i < len; i++) {
483		if (i == len || linebuf[i] == ' ' || linebuf[i] == '\n') {
484			if (i - pos  < 1)
485				continue;
486			if (i - pos >= SYSFS_PATH_MAX)
487				goto error_out;
488			if (current) {
489				current->next = malloc(sizeof(*current));
490				if (!current->next)
491					goto error_out;
492				current = current->next;
493			} else {
494				first = malloc(sizeof(*first));
495				if (!first)
496					return NULL;
497				current = first;
498			}
499			current->first = first;
500			current->next = NULL;
501
502			memcpy(one_value, linebuf + pos, i - pos);
503			one_value[i - pos] = '\0';
504
505			if (sscanf(one_value, "%u", &current->cpu) != 1)
506				goto error_out;
507
508			pos = i + 1;
509		}
510	}
511
512	return first;
513
514 error_out:
515	while (first) {
516		current = first->next;
517		free(first);
518		first = current;
519	}
520	return NULL;
521}
522
523struct cpufreq_affected_cpus *cpufreq_get_affected_cpus(unsigned int cpu)
524{
525	return sysfs_get_cpu_list(cpu, "affected_cpus");
526}
527
528void cpufreq_put_affected_cpus(struct cpufreq_affected_cpus *any)
529{
530	struct cpufreq_affected_cpus *tmp, *next;
531
532	if (!any)
533		return;
534
535	tmp = any->first;
536	while (tmp) {
537		next = tmp->next;
538		free(tmp);
539		tmp = next;
540	}
541}
542
543
544struct cpufreq_affected_cpus *cpufreq_get_related_cpus(unsigned int cpu)
545{
546	return sysfs_get_cpu_list(cpu, "related_cpus");
547}
548
549void cpufreq_put_related_cpus(struct cpufreq_affected_cpus *any)
550{
551	cpufreq_put_affected_cpus(any);
552}
553
554static int verify_gov(char *new_gov, char *passed_gov)
555{
556	unsigned int i, j = 0;
557
558	if (!passed_gov || (strlen(passed_gov) > 19))
559		return -EINVAL;
560
561	strncpy(new_gov, passed_gov, 20);
562	for (i = 0; i < 20; i++) {
563		if (j) {
564			new_gov[i] = '\0';
565			continue;
566		}
567		if ((new_gov[i] >= 'a') && (new_gov[i] <= 'z'))
568			continue;
569
570		if ((new_gov[i] >= 'A') && (new_gov[i] <= 'Z'))
571			continue;
572
573		if (new_gov[i] == '-')
574			continue;
575
576		if (new_gov[i] == '_')
577			continue;
578
579		if (new_gov[i] == '\0') {
580			j = 1;
581			continue;
582		}
583		return -EINVAL;
584	}
585	new_gov[19] = '\0';
586	return 0;
587}
588
589int cpufreq_set_policy(unsigned int cpu, struct cpufreq_policy *policy)
590{
591	char min[SYSFS_PATH_MAX];
592	char max[SYSFS_PATH_MAX];
593	char gov[SYSFS_PATH_MAX];
594	int ret;
595	unsigned long old_min;
596	int write_max_first;
597
598	if (!policy || !(policy->governor))
599		return -EINVAL;
600
601	if (policy->max < policy->min)
602		return -EINVAL;
603
604	if (verify_gov(gov, policy->governor))
605		return -EINVAL;
606
607	snprintf(min, SYSFS_PATH_MAX, "%lu", policy->min);
608	snprintf(max, SYSFS_PATH_MAX, "%lu", policy->max);
609
610	old_min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ);
611	write_max_first = (old_min && (policy->max < old_min) ? 0 : 1);
612
613	if (write_max_first) {
614		ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,
615						    max, strlen(max));
616		if (ret)
617			return ret;
618	}
619
620	ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ, min,
621					    strlen(min));
622	if (ret)
623		return ret;
624
625	if (!write_max_first) {
626		ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,
627						    max, strlen(max));
628		if (ret)
629			return ret;
630	}
631
632	return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR,
633					     gov, strlen(gov));
634}
635
636
637int cpufreq_modify_policy_min(unsigned int cpu, unsigned long min_freq)
638{
639	char value[SYSFS_PATH_MAX];
640
641	snprintf(value, SYSFS_PATH_MAX, "%lu", min_freq);
642
643	return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ,
644					     value, strlen(value));
645}
646
647
648int cpufreq_modify_policy_max(unsigned int cpu, unsigned long max_freq)
649{
650	char value[SYSFS_PATH_MAX];
651
652	snprintf(value, SYSFS_PATH_MAX, "%lu", max_freq);
653
654	return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,
655					     value, strlen(value));
656}
657
658int cpufreq_modify_policy_governor(unsigned int cpu, char *governor)
659{
660	char new_gov[SYSFS_PATH_MAX];
661
662	if ((!governor) || (strlen(governor) > 19))
663		return -EINVAL;
664
665	if (verify_gov(new_gov, governor))
666		return -EINVAL;
667
668	return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR,
669					     new_gov, strlen(new_gov));
670}
671
672int cpufreq_set_frequency(unsigned int cpu, unsigned long target_frequency)
673{
674	struct cpufreq_policy *pol = cpufreq_get_policy(cpu);
675	char userspace_gov[] = "userspace";
676	char freq[SYSFS_PATH_MAX];
677	int ret;
678
679	if (!pol)
680		return -ENODEV;
681
682	if (strncmp(pol->governor, userspace_gov, 9) != 0) {
683		ret = cpufreq_modify_policy_governor(cpu, userspace_gov);
684		if (ret) {
685			cpufreq_put_policy(pol);
686			return ret;
687		}
688	}
689
690	cpufreq_put_policy(pol);
691
692	snprintf(freq, SYSFS_PATH_MAX, "%lu", target_frequency);
693
694	return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_SET_SPEED,
695					     freq, strlen(freq));
696}
697
698struct cpufreq_stats *cpufreq_get_stats(unsigned int cpu,
699					unsigned long long *total_time)
700{
701	struct cpufreq_stats *first = NULL;
702	struct cpufreq_stats *current = NULL;
703	char one_value[SYSFS_PATH_MAX];
704	char linebuf[MAX_LINE_LEN];
705	unsigned int pos, i;
706	unsigned int len;
707
708	len = sysfs_cpufreq_read_file(cpu, "stats/time_in_state",
709				linebuf, sizeof(linebuf));
710	if (len == 0)
711		return NULL;
712
713	*total_time = 0;
714	pos = 0;
715	for (i = 0; i < len; i++) {
716		if (i == strlen(linebuf) || linebuf[i] == '\n')	{
717			if (i - pos < 2)
718				continue;
719			if ((i - pos) >= SYSFS_PATH_MAX)
720				goto error_out;
721			if (current) {
722				current->next = malloc(sizeof(*current));
723				if (!current->next)
724					goto error_out;
725				current = current->next;
726			} else {
727				first = malloc(sizeof(*first));
728				if (!first)
729					return NULL;
730				current = first;
731			}
732			current->first = first;
733			current->next = NULL;
734
735			memcpy(one_value, linebuf + pos, i - pos);
736			one_value[i - pos] = '\0';
737			if (sscanf(one_value, "%lu %llu",
738					&current->frequency,
739					&current->time_in_state) != 2)
740				goto error_out;
741
742			*total_time = *total_time + current->time_in_state;
743			pos = i + 1;
744		}
745	}
746
747	return first;
748
749 error_out:
750	while (first) {
751		current = first->next;
752		free(first);
753		first = current;
754	}
755	return NULL;
756}
757
758void cpufreq_put_stats(struct cpufreq_stats *any)
759{
760	struct cpufreq_stats *tmp, *next;
761
762	if (!any)
763		return;
764
765	tmp = any->first;
766	while (tmp) {
767		next = tmp->next;
768		free(tmp);
769		tmp = next;
770	}
771}
772
773unsigned long cpufreq_get_transitions(unsigned int cpu)
774{
775	return sysfs_cpufreq_get_one_value(cpu, STATS_NUM_TRANSITIONS);
776}
777