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  * Hardcoded, because rapl is the only powercap implementation
82 - * this needs to get more generic if more powercap implementations
83  * should show up
84  */
85 int 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 
101 enum 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 
109 static 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 
116 static 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 
138 int 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 
143 int 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 
148 int 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 
153 int 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 
158 int 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 
173 int powercap_zone_set_enabled(struct powercap_zone *zone, int mode)
174 {
175 	/* To be done if needed */
176 	return 0;
177 }
178 
179 
180 int 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 
254 struct 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 
282 int 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