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 
18 static 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 
39 static 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 	}
67 out_close:
68 	close(fd);
69 out:
70 	return ret;
71 }
72 
73 int 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  * TODO: implement function. Returns dummy 0 for now.
82  */
83 int powercap_set_enabled(int mode)
84 {
85 	return 0;
86 }
87 
88 /*
89  * Hardcoded, because rapl is the only powercap implementation
90 - * this needs to get more generic if more powercap implementations
91  * should show up
92  */
93 int powercap_get_driver(char *driver, int buflen)
94 {
95 	char file[SYSFS_PATH_MAX] = PATH_TO_RAPL;
96 
97 	struct stat statbuf;
98 
99 	if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) {
100 		driver = "";
101 		return -1;
102 	} else if (buflen > 10) {
103 		strcpy(driver, "intel-rapl");
104 		return 0;
105 	} else
106 		return -1;
107 }
108 
109 enum powercap_get64 {
110 	GET_ENERGY_UJ,
111 	GET_MAX_ENERGY_RANGE_UJ,
112 	GET_POWER_UW,
113 	GET_MAX_POWER_RANGE_UW,
114 	MAX_GET_64_FILES
115 };
116 
117 static const char *powercap_get64_files[MAX_GET_64_FILES] = {
118 	[GET_POWER_UW] = "power_uw",
119 	[GET_MAX_POWER_RANGE_UW] = "max_power_range_uw",
120 	[GET_ENERGY_UJ] = "energy_uj",
121 	[GET_MAX_ENERGY_RANGE_UJ] = "max_energy_range_uj",
122 };
123 
124 static int sysfs_powercap_get64_val(struct powercap_zone *zone,
125 				      enum powercap_get64 which,
126 				      uint64_t *val)
127 {
128 	char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP "/";
129 	int ret;
130 	char buf[MAX_LINE_LEN];
131 
132 	strcat(file, zone->sys_name);
133 	strcat(file, "/");
134 	strcat(file, powercap_get64_files[which]);
135 
136 	ret = sysfs_read_file(file, buf, MAX_LINE_LEN);
137 	if (ret < 0)
138 		return ret;
139 	if (ret == 0)
140 		return -1;
141 
142 	*val = strtoll(buf, NULL, 10);
143 	return 0;
144 }
145 
146 int powercap_get_max_energy_range_uj(struct powercap_zone *zone, uint64_t *val)
147 {
148 	return sysfs_powercap_get64_val(zone, GET_MAX_ENERGY_RANGE_UJ, val);
149 }
150 
151 int powercap_get_energy_uj(struct powercap_zone *zone, uint64_t *val)
152 {
153 	return sysfs_powercap_get64_val(zone, GET_ENERGY_UJ, val);
154 }
155 
156 int powercap_get_max_power_range_uw(struct powercap_zone *zone, uint64_t *val)
157 {
158 	return sysfs_powercap_get64_val(zone, GET_MAX_POWER_RANGE_UW, val);
159 }
160 
161 int powercap_get_power_uw(struct powercap_zone *zone, uint64_t *val)
162 {
163 	return sysfs_powercap_get64_val(zone, GET_POWER_UW, val);
164 }
165 
166 int powercap_zone_get_enabled(struct powercap_zone *zone, int *mode)
167 {
168 	char path[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
169 
170 	if ((strlen(PATH_TO_POWERCAP) + strlen(zone->sys_name)) +
171 	    strlen("/enabled") + 1 >= SYSFS_PATH_MAX)
172 		return -1;
173 
174 	strcat(path, "/");
175 	strcat(path, zone->sys_name);
176 	strcat(path, "/enabled");
177 
178 	return sysfs_get_enabled(path, mode);
179 }
180 
181 int powercap_zone_set_enabled(struct powercap_zone *zone, int mode)
182 {
183 	/* To be done if needed */
184 	return 0;
185 }
186 
187 
188 int powercap_read_zone(struct powercap_zone *zone)
189 {
190 	struct dirent *dent;
191 	DIR *zone_dir;
192 	char sysfs_dir[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
193 	struct powercap_zone *child_zone;
194 	char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
195 	int i, ret = 0;
196 	uint64_t val = 0;
197 
198 	strcat(sysfs_dir, "/");
199 	strcat(sysfs_dir, zone->sys_name);
200 
201 	zone_dir = opendir(sysfs_dir);
202 	if (zone_dir == NULL)
203 		return -1;
204 
205 	strcat(file, "/");
206 	strcat(file, zone->sys_name);
207 	strcat(file, "/name");
208 	sysfs_read_file(file, zone->name, MAX_LINE_LEN);
209 	if (zone->parent)
210 		zone->tree_depth = zone->parent->tree_depth + 1;
211 	ret = powercap_get_energy_uj(zone, &val);
212 	if (ret == 0)
213 		zone->has_energy_uj = 1;
214 	ret = powercap_get_power_uw(zone, &val);
215 	if (ret == 0)
216 		zone->has_power_uw = 1;
217 
218 	while ((dent = readdir(zone_dir)) != NULL) {
219 		struct stat st;
220 
221 		if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
222 			continue;
223 
224 		if (stat(dent->d_name, &st) != 0 || !S_ISDIR(st.st_mode))
225 			if (fstatat(dirfd(zone_dir), dent->d_name, &st, 0) < 0)
226 				continue;
227 
228 		if (strncmp(dent->d_name, "intel-rapl:", 11) != 0)
229 			continue;
230 
231 		child_zone = calloc(1, sizeof(struct powercap_zone));
232 		if (child_zone == NULL)
233 			return -1;
234 		for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
235 			if (zone->children[i] == NULL) {
236 				zone->children[i] = child_zone;
237 				break;
238 			}
239 			if (i == POWERCAP_MAX_CHILD_ZONES - 1) {
240 				free(child_zone);
241 				fprintf(stderr, "Reached POWERCAP_MAX_CHILD_ZONES %d\n",
242 				       POWERCAP_MAX_CHILD_ZONES);
243 				return -1;
244 			}
245 		}
246 		strcpy(child_zone->sys_name, zone->sys_name);
247 		strcat(child_zone->sys_name, "/");
248 		strcat(child_zone->sys_name, dent->d_name);
249 		child_zone->parent = zone;
250 		if (zone->tree_depth >= POWERCAP_MAX_TREE_DEPTH) {
251 			fprintf(stderr, "Maximum zone hierarchy depth[%d] reached\n",
252 				POWERCAP_MAX_TREE_DEPTH);
253 			ret = -1;
254 			break;
255 		}
256 		powercap_read_zone(child_zone);
257 	}
258 	closedir(zone_dir);
259 	return ret;
260 }
261 
262 struct powercap_zone *powercap_init_zones(void)
263 {
264 	int enabled;
265 	struct powercap_zone *root_zone;
266 	int ret;
267 	char file[SYSFS_PATH_MAX] = PATH_TO_RAPL "/enabled";
268 
269 	ret = sysfs_get_enabled(file, &enabled);
270 
271 	if (ret)
272 		return NULL;
273 
274 	if (!enabled)
275 		return NULL;
276 
277 	root_zone = calloc(1, sizeof(struct powercap_zone));
278 	if (!root_zone)
279 		return NULL;
280 
281 	strcpy(root_zone->sys_name, "intel-rapl/intel-rapl:0");
282 
283 	powercap_read_zone(root_zone);
284 
285 	return root_zone;
286 }
287 
288 /* Call function *f on the passed zone and all its children */
289 
290 int powercap_walk_zones(struct powercap_zone *zone,
291 			int (*f)(struct powercap_zone *zone))
292 {
293 	int i, ret;
294 
295 	if (!zone)
296 		return -1;
297 
298 	ret = f(zone);
299 	if (ret)
300 		return ret;
301 
302 	for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
303 		if (zone->children[i] != NULL)
304 			powercap_walk_zones(zone->children[i], f);
305 	}
306 	return 0;
307 }
308