1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 *  (C) 2016 SUSE Software Solutions GmbH
4 *           Thomas Renninger <trenn@suse.de>
5 */
6
7#include <sys/types.h>
8#include <sys/stat.h>
9#include <unistd.h>
10#include <stdlib.h>
11#include <string.h>
12#include <fcntl.h>
13#include <stdio.h>
14#include <dirent.h>
15
16#include "powercap.h"
17
18static unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen)
19{
20	int fd;
21	ssize_t numread;
22
23	fd = open(path, O_RDONLY);
24	if (fd == -1)
25		return 0;
26
27	numread = read(fd, buf, buflen - 1);
28	if (numread < 1) {
29		close(fd);
30		return 0;
31	}
32
33	buf[numread] = '\0';
34	close(fd);
35
36	return (unsigned int) numread;
37}
38
39static int sysfs_get_enabled(char *path, int *mode)
40{
41	int fd;
42	char yes_no;
43	int ret = 0;
44
45	*mode = 0;
46
47	fd = open(path, O_RDONLY);
48	if (fd == -1) {
49		ret = -1;
50		goto out;
51	}
52
53	if (read(fd, &yes_no, 1) != 1) {
54		ret = -1;
55		goto out_close;
56	}
57
58	if (yes_no == '1') {
59		*mode = 1;
60		goto out_close;
61	} else if (yes_no == '0') {
62		goto out_close;
63	} else {
64		ret = -1;
65		goto out_close;
66	}
67out_close:
68	close(fd);
69out:
70	return ret;
71}
72
73int powercap_get_enabled(int *mode)
74{
75	char path[SYSFS_PATH_MAX] = PATH_TO_POWERCAP "/intel-rapl/enabled";
76
77	return sysfs_get_enabled(path, mode);
78}
79
80/*
81 * Hardcoded, because rapl is the only powercap implementation
82- * this needs to get more generic if more powercap implementations
83 * should show up
84 */
85int powercap_get_driver(char *driver, int buflen)
86{
87	char file[SYSFS_PATH_MAX] = PATH_TO_RAPL;
88
89	struct stat statbuf;
90
91	if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) {
92		driver = "";
93		return -1;
94	} else if (buflen > 10) {
95		strcpy(driver, "intel-rapl");
96		return 0;
97	} else
98		return -1;
99}
100
101enum powercap_get64 {
102	GET_ENERGY_UJ,
103	GET_MAX_ENERGY_RANGE_UJ,
104	GET_POWER_UW,
105	GET_MAX_POWER_RANGE_UW,
106	MAX_GET_64_FILES
107};
108
109static const char *powercap_get64_files[MAX_GET_64_FILES] = {
110	[GET_POWER_UW] = "power_uw",
111	[GET_MAX_POWER_RANGE_UW] = "max_power_range_uw",
112	[GET_ENERGY_UJ] = "energy_uj",
113	[GET_MAX_ENERGY_RANGE_UJ] = "max_energy_range_uj",
114};
115
116static int sysfs_powercap_get64_val(struct powercap_zone *zone,
117				      enum powercap_get64 which,
118				      uint64_t *val)
119{
120	char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP "/";
121	int ret;
122	char buf[MAX_LINE_LEN];
123
124	strcat(file, zone->sys_name);
125	strcat(file, "/");
126	strcat(file, powercap_get64_files[which]);
127
128	ret = sysfs_read_file(file, buf, MAX_LINE_LEN);
129	if (ret < 0)
130		return ret;
131	if (ret == 0)
132		return -1;
133
134	*val = strtoll(buf, NULL, 10);
135	return 0;
136}
137
138int powercap_get_max_energy_range_uj(struct powercap_zone *zone, uint64_t *val)
139{
140	return sysfs_powercap_get64_val(zone, GET_MAX_ENERGY_RANGE_UJ, val);
141}
142
143int powercap_get_energy_uj(struct powercap_zone *zone, uint64_t *val)
144{
145	return sysfs_powercap_get64_val(zone, GET_ENERGY_UJ, val);
146}
147
148int powercap_get_max_power_range_uw(struct powercap_zone *zone, uint64_t *val)
149{
150	return sysfs_powercap_get64_val(zone, GET_MAX_POWER_RANGE_UW, val);
151}
152
153int powercap_get_power_uw(struct powercap_zone *zone, uint64_t *val)
154{
155	return sysfs_powercap_get64_val(zone, GET_POWER_UW, val);
156}
157
158int powercap_zone_get_enabled(struct powercap_zone *zone, int *mode)
159{
160	char path[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
161
162	if ((strlen(PATH_TO_POWERCAP) + strlen(zone->sys_name)) +
163	    strlen("/enabled") + 1 >= SYSFS_PATH_MAX)
164		return -1;
165
166	strcat(path, "/");
167	strcat(path, zone->sys_name);
168	strcat(path, "/enabled");
169
170	return sysfs_get_enabled(path, mode);
171}
172
173int powercap_zone_set_enabled(struct powercap_zone *zone, int mode)
174{
175	/* To be done if needed */
176	return 0;
177}
178
179
180int powercap_read_zone(struct powercap_zone *zone)
181{
182	struct dirent *dent;
183	DIR *zone_dir;
184	char sysfs_dir[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
185	struct powercap_zone *child_zone;
186	char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
187	int i, ret = 0;
188	uint64_t val = 0;
189
190	strcat(sysfs_dir, "/");
191	strcat(sysfs_dir, zone->sys_name);
192
193	zone_dir = opendir(sysfs_dir);
194	if (zone_dir == NULL)
195		return -1;
196
197	strcat(file, "/");
198	strcat(file, zone->sys_name);
199	strcat(file, "/name");
200	sysfs_read_file(file, zone->name, MAX_LINE_LEN);
201	if (zone->parent)
202		zone->tree_depth = zone->parent->tree_depth + 1;
203	ret = powercap_get_energy_uj(zone, &val);
204	if (ret == 0)
205		zone->has_energy_uj = 1;
206	ret = powercap_get_power_uw(zone, &val);
207	if (ret == 0)
208		zone->has_power_uw = 1;
209
210	while ((dent = readdir(zone_dir)) != NULL) {
211		struct stat st;
212
213		if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
214			continue;
215
216		if (stat(dent->d_name, &st) != 0 || !S_ISDIR(st.st_mode))
217			if (fstatat(dirfd(zone_dir), dent->d_name, &st, 0) < 0)
218				continue;
219
220		if (strncmp(dent->d_name, "intel-rapl:", 11) != 0)
221			continue;
222
223		child_zone = calloc(1, sizeof(struct powercap_zone));
224		if (child_zone == NULL)
225			return -1;
226		for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
227			if (zone->children[i] == NULL) {
228				zone->children[i] = child_zone;
229				break;
230			}
231			if (i == POWERCAP_MAX_CHILD_ZONES - 1) {
232				free(child_zone);
233				fprintf(stderr, "Reached POWERCAP_MAX_CHILD_ZONES %d\n",
234				       POWERCAP_MAX_CHILD_ZONES);
235				return -1;
236			}
237		}
238		strcpy(child_zone->sys_name, zone->sys_name);
239		strcat(child_zone->sys_name, "/");
240		strcat(child_zone->sys_name, dent->d_name);
241		child_zone->parent = zone;
242		if (zone->tree_depth >= POWERCAP_MAX_TREE_DEPTH) {
243			fprintf(stderr, "Maximum zone hierarchy depth[%d] reached\n",
244				POWERCAP_MAX_TREE_DEPTH);
245			ret = -1;
246			break;
247		}
248		powercap_read_zone(child_zone);
249	}
250	closedir(zone_dir);
251	return ret;
252}
253
254struct powercap_zone *powercap_init_zones(void)
255{
256	int enabled;
257	struct powercap_zone *root_zone;
258	int ret;
259	char file[SYSFS_PATH_MAX] = PATH_TO_RAPL "/enabled";
260
261	ret = sysfs_get_enabled(file, &enabled);
262
263	if (ret)
264		return NULL;
265
266	if (!enabled)
267		return NULL;
268
269	root_zone = calloc(1, sizeof(struct powercap_zone));
270	if (!root_zone)
271		return NULL;
272
273	strcpy(root_zone->sys_name, "intel-rapl/intel-rapl:0");
274
275	powercap_read_zone(root_zone);
276
277	return root_zone;
278}
279
280/* Call function *f on the passed zone and all its children */
281
282int powercap_walk_zones(struct powercap_zone *zone,
283			int (*f)(struct powercap_zone *zone))
284{
285	int i, ret;
286
287	if (!zone)
288		return -1;
289
290	ret = f(zone);
291	if (ret)
292		return ret;
293
294	for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
295		if (zone->children[i] != NULL)
296			powercap_walk_zones(zone->children[i], f);
297	}
298	return 0;
299}
300