1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Basic resctrl file system operations |
4 | * |
5 | * Copyright (C) 2018 Intel Corporation |
6 | * |
7 | * Authors: |
8 | * Sai Praneeth Prakhya <sai.praneeth.prakhya@intel.com>, |
9 | * Fenghua Yu <fenghua.yu@intel.com> |
10 | */ |
11 | #include <fcntl.h> |
12 | #include <limits.h> |
13 | |
14 | #include "resctrl.h" |
15 | |
16 | static int find_resctrl_mount(char *buffer) |
17 | { |
18 | FILE *mounts; |
19 | char line[256], *fs, *mntpoint; |
20 | |
21 | mounts = fopen("/proc/mounts" , "r" ); |
22 | if (!mounts) { |
23 | ksft_perror(msg: "/proc/mounts" ); |
24 | return -ENXIO; |
25 | } |
26 | while (!feof(mounts)) { |
27 | if (!fgets(line, 256, mounts)) |
28 | break; |
29 | fs = strtok(line, " \t" ); |
30 | if (!fs) |
31 | continue; |
32 | mntpoint = strtok(NULL, " \t" ); |
33 | if (!mntpoint) |
34 | continue; |
35 | fs = strtok(NULL, " \t" ); |
36 | if (!fs) |
37 | continue; |
38 | if (strcmp(fs, "resctrl" )) |
39 | continue; |
40 | |
41 | fclose(mounts); |
42 | if (buffer) |
43 | strncpy(p: buffer, q: mntpoint, size: 256); |
44 | |
45 | return 0; |
46 | } |
47 | |
48 | fclose(mounts); |
49 | |
50 | return -ENOENT; |
51 | } |
52 | |
53 | /* |
54 | * mount_resctrlfs - Mount resctrl FS at /sys/fs/resctrl |
55 | * |
56 | * Mounts resctrl FS. Fails if resctrl FS is already mounted to avoid |
57 | * pre-existing settings interfering with the test results. |
58 | * |
59 | * Return: 0 on success, < 0 on error. |
60 | */ |
61 | int mount_resctrlfs(void) |
62 | { |
63 | int ret; |
64 | |
65 | ret = find_resctrl_mount(NULL); |
66 | if (ret != -ENOENT) |
67 | return -1; |
68 | |
69 | ksft_print_msg(msg: "Mounting resctrl to \"%s\"\n" , RESCTRL_PATH); |
70 | ret = mount("resctrl" , RESCTRL_PATH, "resctrl" , 0, NULL); |
71 | if (ret) |
72 | ksft_perror(msg: "mount" ); |
73 | |
74 | return ret; |
75 | } |
76 | |
77 | int umount_resctrlfs(void) |
78 | { |
79 | char mountpoint[256]; |
80 | int ret; |
81 | |
82 | ret = find_resctrl_mount(buffer: mountpoint); |
83 | if (ret == -ENOENT) |
84 | return 0; |
85 | if (ret) |
86 | return ret; |
87 | |
88 | if (umount(mountpoint)) { |
89 | ksft_perror(msg: "Unable to umount resctrl" ); |
90 | |
91 | return -1; |
92 | } |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | /* |
98 | * get_cache_level - Convert cache level from string to integer |
99 | * @cache_type: Cache level as string |
100 | * |
101 | * Return: cache level as integer or -1 if @cache_type is invalid. |
102 | */ |
103 | static int get_cache_level(const char *cache_type) |
104 | { |
105 | if (!strcmp(cache_type, "L3" )) |
106 | return 3; |
107 | if (!strcmp(cache_type, "L2" )) |
108 | return 2; |
109 | |
110 | ksft_print_msg(msg: "Invalid cache level\n" ); |
111 | return -1; |
112 | } |
113 | |
114 | static int get_resource_cache_level(const char *resource) |
115 | { |
116 | /* "MB" use L3 (LLC) as resource */ |
117 | if (!strcmp(resource, "MB" )) |
118 | return 3; |
119 | return get_cache_level(cache_type: resource); |
120 | } |
121 | |
122 | /* |
123 | * get_domain_id - Get resctrl domain ID for a specified CPU |
124 | * @resource: resource name |
125 | * @cpu_no: CPU number |
126 | * @domain_id: domain ID (cache ID; for MB, L3 cache ID) |
127 | * |
128 | * Return: >= 0 on success, < 0 on failure. |
129 | */ |
130 | int get_domain_id(const char *resource, int cpu_no, int *domain_id) |
131 | { |
132 | char phys_pkg_path[1024]; |
133 | int cache_num; |
134 | FILE *fp; |
135 | |
136 | cache_num = get_resource_cache_level(resource); |
137 | if (cache_num < 0) |
138 | return cache_num; |
139 | |
140 | sprintf(buf: phys_pkg_path, fmt: "%s%d/cache/index%d/id" , PHYS_ID_PATH, cpu_no, cache_num); |
141 | |
142 | fp = fopen(phys_pkg_path, "r" ); |
143 | if (!fp) { |
144 | ksft_perror(msg: "Failed to open cache id file" ); |
145 | |
146 | return -1; |
147 | } |
148 | if (fscanf(fp, "%d" , domain_id) <= 0) { |
149 | ksft_perror(msg: "Could not get domain ID" ); |
150 | fclose(fp); |
151 | |
152 | return -1; |
153 | } |
154 | fclose(fp); |
155 | |
156 | return 0; |
157 | } |
158 | |
159 | /* |
160 | * get_cache_size - Get cache size for a specified CPU |
161 | * @cpu_no: CPU number |
162 | * @cache_type: Cache level L2/L3 |
163 | * @cache_size: pointer to cache_size |
164 | * |
165 | * Return: = 0 on success, < 0 on failure. |
166 | */ |
167 | int get_cache_size(int cpu_no, const char *cache_type, unsigned long *cache_size) |
168 | { |
169 | char cache_path[1024], cache_str[64]; |
170 | int length, i, cache_num; |
171 | FILE *fp; |
172 | |
173 | cache_num = get_cache_level(cache_type); |
174 | if (cache_num < 0) |
175 | return cache_num; |
176 | |
177 | sprintf(buf: cache_path, fmt: "/sys/bus/cpu/devices/cpu%d/cache/index%d/size" , |
178 | cpu_no, cache_num); |
179 | fp = fopen(cache_path, "r" ); |
180 | if (!fp) { |
181 | ksft_perror(msg: "Failed to open cache size" ); |
182 | |
183 | return -1; |
184 | } |
185 | if (fscanf(fp, "%s" , cache_str) <= 0) { |
186 | ksft_perror(msg: "Could not get cache_size" ); |
187 | fclose(fp); |
188 | |
189 | return -1; |
190 | } |
191 | fclose(fp); |
192 | |
193 | length = (int)strlen(cache_str); |
194 | |
195 | *cache_size = 0; |
196 | |
197 | for (i = 0; i < length; i++) { |
198 | if ((cache_str[i] >= '0') && (cache_str[i] <= '9')) |
199 | |
200 | *cache_size = *cache_size * 10 + (cache_str[i] - '0'); |
201 | |
202 | else if (cache_str[i] == 'K') |
203 | |
204 | *cache_size = *cache_size * 1024; |
205 | |
206 | else if (cache_str[i] == 'M') |
207 | |
208 | *cache_size = *cache_size * 1024 * 1024; |
209 | |
210 | else |
211 | break; |
212 | } |
213 | |
214 | return 0; |
215 | } |
216 | |
217 | #define CORE_SIBLINGS_PATH "/sys/bus/cpu/devices/cpu" |
218 | |
219 | /* |
220 | * get_bit_mask - Get bit mask from given file |
221 | * @filename: File containing the mask |
222 | * @mask: The bit mask returned as unsigned long |
223 | * |
224 | * Return: = 0 on success, < 0 on failure. |
225 | */ |
226 | static int get_bit_mask(const char *filename, unsigned long *mask) |
227 | { |
228 | FILE *fp; |
229 | |
230 | if (!filename || !mask) |
231 | return -1; |
232 | |
233 | fp = fopen(filename, "r" ); |
234 | if (!fp) { |
235 | ksft_print_msg(msg: "Failed to open bit mask file '%s': %s\n" , |
236 | filename, strerror(errno)); |
237 | return -1; |
238 | } |
239 | |
240 | if (fscanf(fp, "%lx" , mask) <= 0) { |
241 | ksft_print_msg(msg: "Could not read bit mask file '%s': %s\n" , |
242 | filename, strerror(errno)); |
243 | fclose(fp); |
244 | |
245 | return -1; |
246 | } |
247 | fclose(fp); |
248 | |
249 | return 0; |
250 | } |
251 | |
252 | /* |
253 | * resource_info_unsigned_get - Read an unsigned value from |
254 | * /sys/fs/resctrl/info/@resource/@filename |
255 | * @resource: Resource name that matches directory name in |
256 | * /sys/fs/resctrl/info |
257 | * @filename: File in /sys/fs/resctrl/info/@resource |
258 | * @val: Contains read value on success. |
259 | * |
260 | * Return: = 0 on success, < 0 on failure. On success the read |
261 | * value is saved into @val. |
262 | */ |
263 | int resource_info_unsigned_get(const char *resource, const char *filename, |
264 | unsigned int *val) |
265 | { |
266 | char file_path[PATH_MAX]; |
267 | FILE *fp; |
268 | |
269 | snprintf(buf: file_path, size: sizeof(file_path), fmt: "%s/%s/%s" , INFO_PATH, resource, |
270 | filename); |
271 | |
272 | fp = fopen(file_path, "r" ); |
273 | if (!fp) { |
274 | ksft_print_msg(msg: "Error opening %s: %m\n" , file_path); |
275 | return -1; |
276 | } |
277 | |
278 | if (fscanf(fp, "%u" , val) <= 0) { |
279 | ksft_print_msg(msg: "Could not get contents of %s: %m\n" , file_path); |
280 | fclose(fp); |
281 | return -1; |
282 | } |
283 | |
284 | fclose(fp); |
285 | return 0; |
286 | } |
287 | |
288 | /* |
289 | * create_bit_mask- Create bit mask from start, len pair |
290 | * @start: LSB of the mask |
291 | * @len Number of bits in the mask |
292 | */ |
293 | unsigned long create_bit_mask(unsigned int start, unsigned int len) |
294 | { |
295 | return ((1UL << len) - 1UL) << start; |
296 | } |
297 | |
298 | /* |
299 | * count_contiguous_bits - Returns the longest train of bits in a bit mask |
300 | * @val A bit mask |
301 | * @start The location of the least-significant bit of the longest train |
302 | * |
303 | * Return: The length of the contiguous bits in the longest train of bits |
304 | */ |
305 | unsigned int count_contiguous_bits(unsigned long val, unsigned int *start) |
306 | { |
307 | unsigned long last_val; |
308 | unsigned int count = 0; |
309 | |
310 | while (val) { |
311 | last_val = val; |
312 | val &= (val >> 1); |
313 | count++; |
314 | } |
315 | |
316 | if (start) { |
317 | if (count) |
318 | *start = ffsl(last_val) - 1; |
319 | else |
320 | *start = 0; |
321 | } |
322 | |
323 | return count; |
324 | } |
325 | |
326 | /* |
327 | * get_full_cbm - Get full Cache Bit Mask (CBM) |
328 | * @cache_type: Cache type as "L2" or "L3" |
329 | * @mask: Full cache bit mask representing the maximal portion of cache |
330 | * available for allocation, returned as unsigned long. |
331 | * |
332 | * Return: = 0 on success, < 0 on failure. |
333 | */ |
334 | int get_full_cbm(const char *cache_type, unsigned long *mask) |
335 | { |
336 | char cbm_path[PATH_MAX]; |
337 | int ret; |
338 | |
339 | if (!cache_type) |
340 | return -1; |
341 | |
342 | snprintf(buf: cbm_path, size: sizeof(cbm_path), fmt: "%s/%s/cbm_mask" , |
343 | INFO_PATH, cache_type); |
344 | |
345 | ret = get_bit_mask(filename: cbm_path, mask); |
346 | if (ret || !*mask) |
347 | return -1; |
348 | |
349 | return 0; |
350 | } |
351 | |
352 | /* |
353 | * get_shareable_mask - Get shareable mask from shareable_bits |
354 | * @cache_type: Cache type as "L2" or "L3" |
355 | * @shareable_mask: Shareable mask returned as unsigned long |
356 | * |
357 | * Return: = 0 on success, < 0 on failure. |
358 | */ |
359 | static int get_shareable_mask(const char *cache_type, unsigned long *shareable_mask) |
360 | { |
361 | char mask_path[PATH_MAX]; |
362 | |
363 | if (!cache_type) |
364 | return -1; |
365 | |
366 | snprintf(buf: mask_path, size: sizeof(mask_path), fmt: "%s/%s/shareable_bits" , |
367 | INFO_PATH, cache_type); |
368 | |
369 | return get_bit_mask(filename: mask_path, mask: shareable_mask); |
370 | } |
371 | |
372 | /* |
373 | * get_mask_no_shareable - Get Cache Bit Mask (CBM) without shareable bits |
374 | * @cache_type: Cache type as "L2" or "L3" |
375 | * @mask: The largest exclusive portion of the cache out of the |
376 | * full CBM, returned as unsigned long |
377 | * |
378 | * Parts of a cache may be shared with other devices such as GPU. This function |
379 | * calculates the largest exclusive portion of the cache where no other devices |
380 | * besides CPU have access to the cache portion. |
381 | * |
382 | * Return: = 0 on success, < 0 on failure. |
383 | */ |
384 | int get_mask_no_shareable(const char *cache_type, unsigned long *mask) |
385 | { |
386 | unsigned long full_mask, shareable_mask; |
387 | unsigned int start, len; |
388 | |
389 | if (get_full_cbm(cache_type, mask: &full_mask) < 0) |
390 | return -1; |
391 | if (get_shareable_mask(cache_type, shareable_mask: &shareable_mask) < 0) |
392 | return -1; |
393 | |
394 | len = count_contiguous_bits(val: full_mask & ~shareable_mask, start: &start); |
395 | if (!len) |
396 | return -1; |
397 | |
398 | *mask = create_bit_mask(start, len); |
399 | |
400 | return 0; |
401 | } |
402 | |
403 | /* |
404 | * taskset_benchmark - Taskset PID (i.e. benchmark) to a specified cpu |
405 | * @bm_pid: PID that should be binded |
406 | * @cpu_no: CPU number at which the PID would be binded |
407 | * @old_affinity: When not NULL, set to old CPU affinity |
408 | * |
409 | * Return: 0 on success, < 0 on error. |
410 | */ |
411 | int taskset_benchmark(pid_t bm_pid, int cpu_no, cpu_set_t *old_affinity) |
412 | { |
413 | cpu_set_t my_set; |
414 | |
415 | if (old_affinity) { |
416 | CPU_ZERO(old_affinity); |
417 | if (sched_getaffinity(pid: bm_pid, mask: sizeof(*old_affinity), |
418 | old_affinity)) { |
419 | ksft_perror(msg: "Unable to read CPU affinity" ); |
420 | return -1; |
421 | } |
422 | } |
423 | |
424 | CPU_ZERO(&my_set); |
425 | CPU_SET(cpu_no, &my_set); |
426 | |
427 | if (sched_setaffinity(bm_pid, sizeof(cpu_set_t), &my_set)) { |
428 | ksft_perror(msg: "Unable to taskset benchmark" ); |
429 | |
430 | return -1; |
431 | } |
432 | |
433 | return 0; |
434 | } |
435 | |
436 | /* |
437 | * taskset_restore - Taskset PID to the earlier CPU affinity |
438 | * @bm_pid: PID that should be reset |
439 | * @old_affinity: The old CPU affinity to restore |
440 | * |
441 | * Return: 0 on success, < 0 on error. |
442 | */ |
443 | int taskset_restore(pid_t bm_pid, cpu_set_t *old_affinity) |
444 | { |
445 | if (sched_setaffinity(pid: bm_pid, new_mask: sizeof(*old_affinity), old_affinity)) { |
446 | ksft_perror(msg: "Unable to restore CPU affinity" ); |
447 | return -1; |
448 | } |
449 | |
450 | return 0; |
451 | } |
452 | |
453 | /* |
454 | * create_grp - Create a group only if one doesn't exist |
455 | * @grp_name: Name of the group |
456 | * @grp: Full path and name of the group |
457 | * @parent_grp: Full path and name of the parent group |
458 | * |
459 | * Return: 0 on success, < 0 on error. |
460 | */ |
461 | static int create_grp(const char *grp_name, char *grp, const char *parent_grp) |
462 | { |
463 | int found_grp = 0; |
464 | struct dirent *ep; |
465 | DIR *dp; |
466 | |
467 | /* |
468 | * At this point, we are guaranteed to have resctrl FS mounted and if |
469 | * length of grp_name == 0, it means, user wants to use root con_mon |
470 | * grp, so do nothing |
471 | */ |
472 | if (strlen(grp_name) == 0) |
473 | return 0; |
474 | |
475 | /* Check if requested grp exists or not */ |
476 | dp = opendir(parent_grp); |
477 | if (dp) { |
478 | while ((ep = readdir(dp)) != NULL) { |
479 | if (strcmp(ep->d_name, grp_name) == 0) |
480 | found_grp = 1; |
481 | } |
482 | closedir(dp); |
483 | } else { |
484 | ksft_perror(msg: "Unable to open resctrl for group" ); |
485 | |
486 | return -1; |
487 | } |
488 | |
489 | /* Requested grp doesn't exist, hence create it */ |
490 | if (found_grp == 0) { |
491 | if (mkdir(grp, 0) == -1) { |
492 | ksft_perror(msg: "Unable to create group" ); |
493 | |
494 | return -1; |
495 | } |
496 | } |
497 | |
498 | return 0; |
499 | } |
500 | |
501 | static int write_pid_to_tasks(char *tasks, pid_t pid) |
502 | { |
503 | FILE *fp; |
504 | |
505 | fp = fopen(tasks, "w" ); |
506 | if (!fp) { |
507 | ksft_perror(msg: "Failed to open tasks file" ); |
508 | |
509 | return -1; |
510 | } |
511 | if (fprintf(fp, "%d\n" , pid) < 0) { |
512 | ksft_print_msg(msg: "Failed to write pid to tasks file\n" ); |
513 | fclose(fp); |
514 | |
515 | return -1; |
516 | } |
517 | fclose(fp); |
518 | |
519 | return 0; |
520 | } |
521 | |
522 | /* |
523 | * write_bm_pid_to_resctrl - Write a PID (i.e. benchmark) to resctrl FS |
524 | * @bm_pid: PID that should be written |
525 | * @ctrlgrp: Name of the control monitor group (con_mon grp) |
526 | * @mongrp: Name of the monitor group (mon grp) |
527 | * @resctrl_val: Resctrl feature (Eg: mbm, mba.. etc) |
528 | * |
529 | * If a con_mon grp is requested, create it and write pid to it, otherwise |
530 | * write pid to root con_mon grp. |
531 | * If a mon grp is requested, create it and write pid to it, otherwise |
532 | * pid is not written, this means that pid is in con_mon grp and hence |
533 | * should consult con_mon grp's mon_data directory for results. |
534 | * |
535 | * Return: 0 on success, < 0 on error. |
536 | */ |
537 | int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp, |
538 | char *resctrl_val) |
539 | { |
540 | char controlgroup[128], monitorgroup[512], monitorgroup_p[256]; |
541 | char tasks[1024]; |
542 | int ret = 0; |
543 | |
544 | if (strlen(ctrlgrp)) |
545 | sprintf(buf: controlgroup, fmt: "%s/%s" , RESCTRL_PATH, ctrlgrp); |
546 | else |
547 | sprintf(buf: controlgroup, fmt: "%s" , RESCTRL_PATH); |
548 | |
549 | /* Create control and monitoring group and write pid into it */ |
550 | ret = create_grp(grp_name: ctrlgrp, grp: controlgroup, RESCTRL_PATH); |
551 | if (ret) |
552 | goto out; |
553 | sprintf(buf: tasks, fmt: "%s/tasks" , controlgroup); |
554 | ret = write_pid_to_tasks(tasks, pid: bm_pid); |
555 | if (ret) |
556 | goto out; |
557 | |
558 | /* Create mon grp and write pid into it for "mbm" and "cmt" test */ |
559 | if (!strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR)) || |
560 | !strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR))) { |
561 | if (strlen(mongrp)) { |
562 | sprintf(buf: monitorgroup_p, fmt: "%s/mon_groups" , controlgroup); |
563 | sprintf(buf: monitorgroup, fmt: "%s/%s" , monitorgroup_p, mongrp); |
564 | ret = create_grp(grp_name: mongrp, grp: monitorgroup, parent_grp: monitorgroup_p); |
565 | if (ret) |
566 | goto out; |
567 | |
568 | sprintf(buf: tasks, fmt: "%s/mon_groups/%s/tasks" , |
569 | controlgroup, mongrp); |
570 | ret = write_pid_to_tasks(tasks, pid: bm_pid); |
571 | if (ret) |
572 | goto out; |
573 | } |
574 | } |
575 | |
576 | out: |
577 | ksft_print_msg(msg: "Writing benchmark parameters to resctrl FS\n" ); |
578 | if (ret) |
579 | ksft_print_msg(msg: "Failed writing to resctrlfs\n" ); |
580 | |
581 | return ret; |
582 | } |
583 | |
584 | /* |
585 | * write_schemata - Update schemata of a con_mon grp |
586 | * @ctrlgrp: Name of the con_mon grp |
587 | * @schemata: Schemata that should be updated to |
588 | * @cpu_no: CPU number that the benchmark PID is binded to |
589 | * @resource: Resctrl resource (Eg: MB, L3, L2, etc.) |
590 | * |
591 | * Update schemata of a con_mon grp *only* if requested resctrl resource is |
592 | * allocation type |
593 | * |
594 | * Return: 0 on success, < 0 on error. |
595 | */ |
596 | int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, const char *resource) |
597 | { |
598 | char controlgroup[1024], reason[128], schema[1024] = {}; |
599 | int domain_id, fd, schema_len, ret = 0; |
600 | |
601 | if (!schemata) { |
602 | ksft_print_msg(msg: "Skipping empty schemata update\n" ); |
603 | |
604 | return -1; |
605 | } |
606 | |
607 | if (get_domain_id(resource, cpu_no, domain_id: &domain_id) < 0) { |
608 | sprintf(buf: reason, fmt: "Failed to get domain ID" ); |
609 | ret = -1; |
610 | |
611 | goto out; |
612 | } |
613 | |
614 | if (strlen(ctrlgrp) != 0) |
615 | sprintf(buf: controlgroup, fmt: "%s/%s/schemata" , RESCTRL_PATH, ctrlgrp); |
616 | else |
617 | sprintf(buf: controlgroup, fmt: "%s/schemata" , RESCTRL_PATH); |
618 | |
619 | schema_len = snprintf(buf: schema, size: sizeof(schema), fmt: "%s:%d=%s\n" , |
620 | resource, domain_id, schemata); |
621 | if (schema_len < 0 || schema_len >= sizeof(schema)) { |
622 | snprintf(buf: reason, size: sizeof(reason), |
623 | fmt: "snprintf() failed with return value : %d" , schema_len); |
624 | ret = -1; |
625 | goto out; |
626 | } |
627 | |
628 | fd = open(controlgroup, O_WRONLY); |
629 | if (fd < 0) { |
630 | snprintf(reason, sizeof(reason), |
631 | "open() failed : %s" , strerror(errno)); |
632 | ret = -1; |
633 | |
634 | goto err_schema_not_empty; |
635 | } |
636 | if (write(fd, schema, schema_len) < 0) { |
637 | snprintf(reason, sizeof(reason), |
638 | "write() failed : %s" , strerror(errno)); |
639 | close(fd); |
640 | ret = -1; |
641 | |
642 | goto err_schema_not_empty; |
643 | } |
644 | close(fd); |
645 | |
646 | err_schema_not_empty: |
647 | schema[schema_len - 1] = 0; |
648 | out: |
649 | ksft_print_msg(msg: "Write schema \"%s\" to resctrl FS%s%s\n" , |
650 | schema, ret ? " # " : "" , |
651 | ret ? reason : "" ); |
652 | |
653 | return ret; |
654 | } |
655 | |
656 | bool check_resctrlfs_support(void) |
657 | { |
658 | FILE *inf = fopen("/proc/filesystems" , "r" ); |
659 | DIR *dp; |
660 | char *res; |
661 | bool ret = false; |
662 | |
663 | if (!inf) |
664 | return false; |
665 | |
666 | res = fgrep(inf, "nodev\tresctrl\n" ); |
667 | |
668 | if (res) { |
669 | ret = true; |
670 | free(res); |
671 | } |
672 | |
673 | fclose(inf); |
674 | |
675 | ksft_print_msg(msg: "%s Check kernel supports resctrl filesystem\n" , |
676 | ret ? "Pass:" : "Fail:" ); |
677 | |
678 | if (!ret) |
679 | return ret; |
680 | |
681 | dp = opendir(RESCTRL_PATH); |
682 | ksft_print_msg("%s Check resctrl mountpoint \"%s\" exists\n" , |
683 | dp ? "Pass:" : "Fail:" , RESCTRL_PATH); |
684 | if (dp) |
685 | closedir(dp); |
686 | |
687 | ksft_print_msg(msg: "resctrl filesystem %s mounted\n" , |
688 | find_resctrl_mount(NULL) ? "not" : "is" ); |
689 | |
690 | return ret; |
691 | } |
692 | |
693 | char *fgrep(FILE *inf, const char *str) |
694 | { |
695 | char line[256]; |
696 | int slen = strlen(str); |
697 | |
698 | while (!feof(inf)) { |
699 | if (!fgets(line, 256, inf)) |
700 | break; |
701 | if (strncmp(line, str, slen)) |
702 | continue; |
703 | |
704 | return strdup(line); |
705 | } |
706 | |
707 | return NULL; |
708 | } |
709 | |
710 | /* |
711 | * resctrl_resource_exists - Check if a resource is supported. |
712 | * @resource: Resctrl resource (e.g., MB, L3, L2, L3_MON, etc.) |
713 | * |
714 | * Return: True if the resource is supported, else false. False is |
715 | * also returned if resctrl FS is not mounted. |
716 | */ |
717 | bool resctrl_resource_exists(const char *resource) |
718 | { |
719 | char res_path[PATH_MAX]; |
720 | struct stat statbuf; |
721 | int ret; |
722 | |
723 | if (!resource) |
724 | return false; |
725 | |
726 | ret = find_resctrl_mount(NULL); |
727 | if (ret) |
728 | return false; |
729 | |
730 | snprintf(buf: res_path, size: sizeof(res_path), fmt: "%s/%s" , INFO_PATH, resource); |
731 | |
732 | if (stat(res_path, &statbuf)) |
733 | return false; |
734 | |
735 | return true; |
736 | } |
737 | |
738 | /* |
739 | * resctrl_mon_feature_exists - Check if requested monitoring feature is valid. |
740 | * @resource: Resource that uses the mon_features file. Currently only L3_MON |
741 | * is valid. |
742 | * @feature: Required monitor feature (in mon_features file). |
743 | * |
744 | * Return: True if the feature is supported, else false. |
745 | */ |
746 | bool resctrl_mon_feature_exists(const char *resource, const char *feature) |
747 | { |
748 | char res_path[PATH_MAX]; |
749 | char *res; |
750 | FILE *inf; |
751 | |
752 | if (!feature || !resource) |
753 | return false; |
754 | |
755 | snprintf(buf: res_path, size: sizeof(res_path), fmt: "%s/%s/mon_features" , INFO_PATH, resource); |
756 | inf = fopen(res_path, "r" ); |
757 | if (!inf) |
758 | return false; |
759 | |
760 | res = fgrep(inf, feature); |
761 | free(res); |
762 | fclose(inf); |
763 | |
764 | return !!res; |
765 | } |
766 | |
767 | /* |
768 | * resource_info_file_exists - Check if a file is present inside |
769 | * /sys/fs/resctrl/info/@resource. |
770 | * @resource: Required resource (Eg: MB, L3, L2, etc.) |
771 | * @file: Required file. |
772 | * |
773 | * Return: True if the /sys/fs/resctrl/info/@resource/@file exists, else false. |
774 | */ |
775 | bool resource_info_file_exists(const char *resource, const char *file) |
776 | { |
777 | char res_path[PATH_MAX]; |
778 | struct stat statbuf; |
779 | |
780 | if (!file || !resource) |
781 | return false; |
782 | |
783 | snprintf(buf: res_path, size: sizeof(res_path), fmt: "%s/%s/%s" , INFO_PATH, resource, |
784 | file); |
785 | |
786 | if (stat(res_path, &statbuf)) |
787 | return false; |
788 | |
789 | return true; |
790 | } |
791 | |
792 | bool test_resource_feature_check(const struct resctrl_test *test) |
793 | { |
794 | return resctrl_resource_exists(resource: test->resource); |
795 | } |
796 | |
797 | int filter_dmesg(void) |
798 | { |
799 | char line[1024]; |
800 | FILE *fp; |
801 | int pipefds[2]; |
802 | pid_t pid; |
803 | int ret; |
804 | |
805 | ret = pipe(pipefds); |
806 | if (ret) { |
807 | ksft_perror(msg: "pipe" ); |
808 | return ret; |
809 | } |
810 | fflush(stdout); |
811 | pid = fork(); |
812 | if (pid == 0) { |
813 | close(pipefds[0]); |
814 | dup2(pipefds[1], STDOUT_FILENO); |
815 | execlp("dmesg" , "dmesg" , NULL); |
816 | ksft_perror(msg: "Executing dmesg" ); |
817 | exit(1); |
818 | } |
819 | close(pipefds[1]); |
820 | fp = fdopen(pipefds[0], "r" ); |
821 | if (!fp) { |
822 | ksft_perror(msg: "fdopen(pipe)" ); |
823 | kill(pid, SIGTERM); |
824 | |
825 | return -1; |
826 | } |
827 | |
828 | while (fgets(line, 1024, fp)) { |
829 | if (strstr(line, "intel_rdt:" )) |
830 | ksft_print_msg(msg: "dmesg: %s" , line); |
831 | if (strstr(line, "resctrl:" )) |
832 | ksft_print_msg(msg: "dmesg: %s" , line); |
833 | } |
834 | fclose(fp); |
835 | waitpid(pid, NULL, 0); |
836 | |
837 | return 0; |
838 | } |
839 | |
840 | int validate_bw_report_request(char *bw_report) |
841 | { |
842 | if (strcmp(bw_report, "reads" ) == 0) |
843 | return 0; |
844 | if (strcmp(bw_report, "writes" ) == 0) |
845 | return 0; |
846 | if (strcmp(bw_report, "nt-writes" ) == 0) { |
847 | strcpy(p: bw_report, q: "writes" ); |
848 | return 0; |
849 | } |
850 | if (strcmp(bw_report, "total" ) == 0) |
851 | return 0; |
852 | |
853 | fprintf(stderr, "Requested iMC B/W report type unavailable\n" ); |
854 | |
855 | return -1; |
856 | } |
857 | |
858 | int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, |
859 | int group_fd, unsigned long flags) |
860 | { |
861 | int ret; |
862 | |
863 | ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, |
864 | group_fd, flags); |
865 | return ret; |
866 | } |
867 | |
868 | unsigned int count_bits(unsigned long n) |
869 | { |
870 | unsigned int count = 0; |
871 | |
872 | while (n) { |
873 | count += n & 1; |
874 | n >>= 1; |
875 | } |
876 | |
877 | return count; |
878 | } |
879 | |