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