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(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(path: file, mode: &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(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: zone->children[i], f); |
297 | } |
298 | return 0; |
299 | } |
300 | |