// SPDX-License-Identifier: GPL-2.0 /* * Basic resctrl file system operations * * Copyright (C) 2018 Intel Corporation * * Authors: * Sai Praneeth Prakhya , * Fenghua Yu */ #include #include #include "resctrl.h" static int find_resctrl_mount(char *buffer) { FILE *mounts; char line[256], *fs, *mntpoint; mounts = fopen("/proc/mounts", "r"); if (!mounts) { ksft_perror("/proc/mounts"); return -ENXIO; } while (!feof(mounts)) { if (!fgets(line, 256, mounts)) break; fs = strtok(line, " \t"); if (!fs) continue; mntpoint = strtok(NULL, " \t"); if (!mntpoint) continue; fs = strtok(NULL, " \t"); if (!fs) continue; if (strcmp(fs, "resctrl")) continue; fclose(mounts); if (buffer) strncpy(buffer, mntpoint, 256); return 0; } fclose(mounts); return -ENOENT; } /* * mount_resctrlfs - Mount resctrl FS at /sys/fs/resctrl * * Mounts resctrl FS. Fails if resctrl FS is already mounted to avoid * pre-existing settings interfering with the test results. * * Return: 0 on success, < 0 on error. */ int mount_resctrlfs(void) { int ret; ret = find_resctrl_mount(NULL); if (ret != -ENOENT) return -1; ksft_print_msg("Mounting resctrl to \"%s\"\n", RESCTRL_PATH); ret = mount("resctrl", RESCTRL_PATH, "resctrl", 0, NULL); if (ret) ksft_perror("mount"); return ret; } int umount_resctrlfs(void) { char mountpoint[256]; int ret; ret = find_resctrl_mount(mountpoint); if (ret == -ENOENT) return 0; if (ret) return ret; if (umount(mountpoint)) { ksft_perror("Unable to umount resctrl"); return -1; } return 0; } /* * get_cache_level - Convert cache level from string to integer * @cache_type: Cache level as string * * Return: cache level as integer or -1 if @cache_type is invalid. */ static int get_cache_level(const char *cache_type) { if (!strcmp(cache_type, "L3")) return 3; if (!strcmp(cache_type, "L2")) return 2; ksft_print_msg("Invalid cache level\n"); return -1; } static int get_resource_cache_level(const char *resource) { /* "MB" use L3 (LLC) as resource */ if (!strcmp(resource, "MB")) return 3; return get_cache_level(resource); } /* * get_domain_id - Get resctrl domain ID for a specified CPU * @resource: resource name * @cpu_no: CPU number * @domain_id: domain ID (cache ID; for MB, L3 cache ID) * * Return: >= 0 on success, < 0 on failure. */ int get_domain_id(const char *resource, int cpu_no, int *domain_id) { char phys_pkg_path[1024]; int cache_num; FILE *fp; cache_num = get_resource_cache_level(resource); if (cache_num < 0) return cache_num; sprintf(phys_pkg_path, "%s%d/cache/index%d/id", PHYS_ID_PATH, cpu_no, cache_num); fp = fopen(phys_pkg_path, "r"); if (!fp) { ksft_perror("Failed to open cache id file"); return -1; } if (fscanf(fp, "%d", domain_id) <= 0) { ksft_perror("Could not get domain ID"); fclose(fp); return -1; } fclose(fp); return 0; } /* * get_cache_size - Get cache size for a specified CPU * @cpu_no: CPU number * @cache_type: Cache level L2/L3 * @cache_size: pointer to cache_size * * Return: = 0 on success, < 0 on failure. */ int get_cache_size(int cpu_no, const char *cache_type, unsigned long *cache_size) { char cache_path[1024], cache_str[64]; int length, i, cache_num; FILE *fp; cache_num = get_cache_level(cache_type); if (cache_num < 0) return cache_num; sprintf(cache_path, "/sys/bus/cpu/devices/cpu%d/cache/index%d/size", cpu_no, cache_num); fp = fopen(cache_path, "r"); if (!fp) { ksft_perror("Failed to open cache size"); return -1; } if (fscanf(fp, "%s", cache_str) <= 0) { ksft_perror("Could not get cache_size"); fclose(fp); return -1; } fclose(fp); length = (int)strlen(cache_str); *cache_size = 0; for (i = 0; i < length; i++) { if ((cache_str[i] >= '0') && (cache_str[i] <= '9')) *cache_size = *cache_size * 10 + (cache_str[i] - '0'); else if (cache_str[i] == 'K') *cache_size = *cache_size * 1024; else if (cache_str[i] == 'M') *cache_size = *cache_size * 1024 * 1024; else break; } return 0; } #define CORE_SIBLINGS_PATH "/sys/bus/cpu/devices/cpu" /* * get_bit_mask - Get bit mask from given file * @filename: File containing the mask * @mask: The bit mask returned as unsigned long * * Return: = 0 on success, < 0 on failure. */ static int get_bit_mask(const char *filename, unsigned long *mask) { FILE *fp; if (!filename || !mask) return -1; fp = fopen(filename, "r"); if (!fp) { ksft_print_msg("Failed to open bit mask file '%s': %s\n", filename, strerror(errno)); return -1; } if (fscanf(fp, "%lx", mask) <= 0) { ksft_print_msg("Could not read bit mask file '%s': %s\n", filename, strerror(errno)); fclose(fp); return -1; } fclose(fp); return 0; } /* * resource_info_unsigned_get - Read an unsigned value from * /sys/fs/resctrl/info/@resource/@filename * @resource: Resource name that matches directory name in * /sys/fs/resctrl/info * @filename: File in /sys/fs/resctrl/info/@resource * @val: Contains read value on success. * * Return: = 0 on success, < 0 on failure. On success the read * value is saved into @val. */ int resource_info_unsigned_get(const char *resource, const char *filename, unsigned int *val) { char file_path[PATH_MAX]; FILE *fp; snprintf(file_path, sizeof(file_path), "%s/%s/%s", INFO_PATH, resource, filename); fp = fopen(file_path, "r"); if (!fp) { ksft_print_msg("Error opening %s: %m\n", file_path); return -1; } if (fscanf(fp, "%u", val) <= 0) { ksft_print_msg("Could not get contents of %s: %m\n", file_path); fclose(fp); return -1; } fclose(fp); return 0; } /* * create_bit_mask- Create bit mask from start, len pair * @start: LSB of the mask * @len Number of bits in the mask */ unsigned long create_bit_mask(unsigned int start, unsigned int len) { return ((1UL << len) - 1UL) << start; } /* * count_contiguous_bits - Returns the longest train of bits in a bit mask * @val A bit mask * @start The location of the least-significant bit of the longest train * * Return: The length of the contiguous bits in the longest train of bits */ unsigned int count_contiguous_bits(unsigned long val, unsigned int *start) { unsigned long last_val; unsigned int count = 0; while (val) { last_val = val; val &= (val >> 1); count++; } if (start) { if (count) *start = ffsl(last_val) - 1; else *start = 0; } return count; } /* * get_full_cbm - Get full Cache Bit Mask (CBM) * @cache_type: Cache type as "L2" or "L3" * @mask: Full cache bit mask representing the maximal portion of cache * available for allocation, returned as unsigned long. * * Return: = 0 on success, < 0 on failure. */ int get_full_cbm(const char *cache_type, unsigned long *mask) { char cbm_path[PATH_MAX]; int ret; if (!cache_type) return -1; snprintf(cbm_path, sizeof(cbm_path), "%s/%s/cbm_mask", INFO_PATH, cache_type); ret = get_bit_mask(cbm_path, mask); if (ret || !*mask) return -1; return 0; } /* * get_shareable_mask - Get shareable mask from shareable_bits * @cache_type: Cache type as "L2" or "L3" * @shareable_mask: Shareable mask returned as unsigned long * * Return: = 0 on success, < 0 on failure. */ static int get_shareable_mask(const char *cache_type, unsigned long *shareable_mask) { char mask_path[PATH_MAX]; if (!cache_type) return -1; snprintf(mask_path, sizeof(mask_path), "%s/%s/shareable_bits", INFO_PATH, cache_type); return get_bit_mask(mask_path, shareable_mask); } /* * get_mask_no_shareable - Get Cache Bit Mask (CBM) without shareable bits * @cache_type: Cache type as "L2" or "L3" * @mask: The largest exclusive portion of the cache out of the * full CBM, returned as unsigned long * * Parts of a cache may be shared with other devices such as GPU. This function * calculates the largest exclusive portion of the cache where no other devices * besides CPU have access to the cache portion. * * Return: = 0 on success, < 0 on failure. */ int get_mask_no_shareable(const char *cache_type, unsigned long *mask) { unsigned long full_mask, shareable_mask; unsigned int start, len; if (get_full_cbm(cache_type, &full_mask) < 0) return -1; if (get_shareable_mask(cache_type, &shareable_mask) < 0) return -1; len = count_contiguous_bits(full_mask & ~shareable_mask, &start); if (!len) return -1; *mask = create_bit_mask(start, len); return 0; } /* * taskset_benchmark - Taskset PID (i.e. benchmark) to a specified cpu * @bm_pid: PID that should be binded * @cpu_no: CPU number at which the PID would be binded * @old_affinity: When not NULL, set to old CPU affinity * * Return: 0 on success, < 0 on error. */ int taskset_benchmark(pid_t bm_pid, int cpu_no, cpu_set_t *old_affinity) { cpu_set_t my_set; if (old_affinity) { CPU_ZERO(old_affinity); if (sched_getaffinity(bm_pid, sizeof(*old_affinity), old_affinity)) { ksft_perror("Unable to read CPU affinity"); return -1; } } CPU_ZERO(&my_set); CPU_SET(cpu_no, &my_set); if (sched_setaffinity(bm_pid, sizeof(cpu_set_t), &my_set)) { ksft_perror("Unable to taskset benchmark"); return -1; } return 0; } /* * taskset_restore - Taskset PID to the earlier CPU affinity * @bm_pid: PID that should be reset * @old_affinity: The old CPU affinity to restore * * Return: 0 on success, < 0 on error. */ int taskset_restore(pid_t bm_pid, cpu_set_t *old_affinity) { if (sched_setaffinity(bm_pid, sizeof(*old_affinity), old_affinity)) { ksft_perror("Unable to restore CPU affinity"); return -1; } return 0; } /* * create_grp - Create a group only if one doesn't exist * @grp_name: Name of the group * @grp: Full path and name of the group * @parent_grp: Full path and name of the parent group * * Return: 0 on success, < 0 on error. */ static int create_grp(const char *grp_name, char *grp, const char *parent_grp) { int found_grp = 0; struct dirent *ep; DIR *dp; /* * At this point, we are guaranteed to have resctrl FS mounted and if * length of grp_name == 0, it means, user wants to use root con_mon * grp, so do nothing */ if (strlen(grp_name) == 0) return 0; /* Check if requested grp exists or not */ dp = opendir(parent_grp); if (dp) { while ((ep = readdir(dp)) != NULL) { if (strcmp(ep->d_name, grp_name) == 0) found_grp = 1; } closedir(dp); } else { ksft_perror("Unable to open resctrl for group"); return -1; } /* Requested grp doesn't exist, hence create it */ if (found_grp == 0) { if (mkdir(grp, 0) == -1) { ksft_perror("Unable to create group"); return -1; } } return 0; } static int write_pid_to_tasks(char *tasks, pid_t pid) { FILE *fp; fp = fopen(tasks, "w"); if (!fp) { ksft_perror("Failed to open tasks file"); return -1; } if (fprintf(fp, "%d\n", pid) < 0) { ksft_print_msg("Failed to write pid to tasks file\n"); fclose(fp); return -1; } fclose(fp); return 0; } /* * write_bm_pid_to_resctrl - Write a PID (i.e. benchmark) to resctrl FS * @bm_pid: PID that should be written * @ctrlgrp: Name of the control monitor group (con_mon grp) * @mongrp: Name of the monitor group (mon grp) * @resctrl_val: Resctrl feature (Eg: mbm, mba.. etc) * * If a con_mon grp is requested, create it and write pid to it, otherwise * write pid to root con_mon grp. * If a mon grp is requested, create it and write pid to it, otherwise * pid is not written, this means that pid is in con_mon grp and hence * should consult con_mon grp's mon_data directory for results. * * Return: 0 on success, < 0 on error. */ int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp, char *resctrl_val) { char controlgroup[128], monitorgroup[512], monitorgroup_p[256]; char tasks[1024]; int ret = 0; if (strlen(ctrlgrp)) sprintf(controlgroup, "%s/%s", RESCTRL_PATH, ctrlgrp); else sprintf(controlgroup, "%s", RESCTRL_PATH); /* Create control and monitoring group and write pid into it */ ret = create_grp(ctrlgrp, controlgroup, RESCTRL_PATH); if (ret) goto out; sprintf(tasks, "%s/tasks", controlgroup); ret = write_pid_to_tasks(tasks, bm_pid); if (ret) goto out; /* Create mon grp and write pid into it for "mbm" and "cmt" test */ if (!strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR)) || !strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR))) { if (strlen(mongrp)) { sprintf(monitorgroup_p, "%s/mon_groups", controlgroup); sprintf(monitorgroup, "%s/%s", monitorgroup_p, mongrp); ret = create_grp(mongrp, monitorgroup, monitorgroup_p); if (ret) goto out; sprintf(tasks, "%s/mon_groups/%s/tasks", controlgroup, mongrp); ret = write_pid_to_tasks(tasks, bm_pid); if (ret) goto out; } } out: ksft_print_msg("Writing benchmark parameters to resctrl FS\n"); if (ret) ksft_print_msg("Failed writing to resctrlfs\n"); return ret; } /* * write_schemata - Update schemata of a con_mon grp * @ctrlgrp: Name of the con_mon grp * @schemata: Schemata that should be updated to * @cpu_no: CPU number that the benchmark PID is binded to * @resource: Resctrl resource (Eg: MB, L3, L2, etc.) * * Update schemata of a con_mon grp *only* if requested resctrl resource is * allocation type * * Return: 0 on success, < 0 on error. */ int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, const char *resource) { char controlgroup[1024], reason[128], schema[1024] = {}; int domain_id, fd, schema_len, ret = 0; if (!schemata) { ksft_print_msg("Skipping empty schemata update\n"); return -1; } if (get_domain_id(resource, cpu_no, &domain_id) < 0) { sprintf(reason, "Failed to get domain ID"); ret = -1; goto out; } if (strlen(ctrlgrp) != 0) sprintf(controlgroup, "%s/%s/schemata", RESCTRL_PATH, ctrlgrp); else sprintf(controlgroup, "%s/schemata", RESCTRL_PATH); schema_len = snprintf(schema, sizeof(schema), "%s:%d=%s\n", resource, domain_id, schemata); if (schema_len < 0 || schema_len >= sizeof(schema)) { snprintf(reason, sizeof(reason), "snprintf() failed with return value : %d", schema_len); ret = -1; goto out; } fd = open(controlgroup, O_WRONLY); if (fd < 0) { snprintf(reason, sizeof(reason), "open() failed : %s", strerror(errno)); ret = -1; goto err_schema_not_empty; } if (write(fd, schema, schema_len) < 0) { snprintf(reason, sizeof(reason), "write() failed : %s", strerror(errno)); close(fd); ret = -1; goto err_schema_not_empty; } close(fd); err_schema_not_empty: schema[schema_len - 1] = 0; out: ksft_print_msg("Write schema \"%s\" to resctrl FS%s%s\n", schema, ret ? " # " : "", ret ? reason : ""); return ret; } bool check_resctrlfs_support(void) { FILE *inf = fopen("/proc/filesystems", "r"); DIR *dp; char *res; bool ret = false; if (!inf) return false; res = fgrep(inf, "nodev\tresctrl\n"); if (res) { ret = true; free(res); } fclose(inf); ksft_print_msg("%s Check kernel supports resctrl filesystem\n", ret ? "Pass:" : "Fail:"); if (!ret) return ret; dp = opendir(RESCTRL_PATH); ksft_print_msg("%s Check resctrl mountpoint \"%s\" exists\n", dp ? "Pass:" : "Fail:", RESCTRL_PATH); if (dp) closedir(dp); ksft_print_msg("resctrl filesystem %s mounted\n", find_resctrl_mount(NULL) ? "not" : "is"); return ret; } char *fgrep(FILE *inf, const char *str) { char line[256]; int slen = strlen(str); while (!feof(inf)) { if (!fgets(line, 256, inf)) break; if (strncmp(line, str, slen)) continue; return strdup(line); } return NULL; } /* * resctrl_resource_exists - Check if a resource is supported. * @resource: Resctrl resource (e.g., MB, L3, L2, L3_MON, etc.) * * Return: True if the resource is supported, else false. False is * also returned if resctrl FS is not mounted. */ bool resctrl_resource_exists(const char *resource) { char res_path[PATH_MAX]; struct stat statbuf; int ret; if (!resource) return false; ret = find_resctrl_mount(NULL); if (ret) return false; snprintf(res_path, sizeof(res_path), "%s/%s", INFO_PATH, resource); if (stat(res_path, &statbuf)) return false; return true; } /* * resctrl_mon_feature_exists - Check if requested monitoring feature is valid. * @resource: Resource that uses the mon_features file. Currently only L3_MON * is valid. * @feature: Required monitor feature (in mon_features file). * * Return: True if the feature is supported, else false. */ bool resctrl_mon_feature_exists(const char *resource, const char *feature) { char res_path[PATH_MAX]; char *res; FILE *inf; if (!feature || !resource) return false; snprintf(res_path, sizeof(res_path), "%s/%s/mon_features", INFO_PATH, resource); inf = fopen(res_path, "r"); if (!inf) return false; res = fgrep(inf, feature); free(res); fclose(inf); return !!res; } /* * resource_info_file_exists - Check if a file is present inside * /sys/fs/resctrl/info/@resource. * @resource: Required resource (Eg: MB, L3, L2, etc.) * @file: Required file. * * Return: True if the /sys/fs/resctrl/info/@resource/@file exists, else false. */ bool resource_info_file_exists(const char *resource, const char *file) { char res_path[PATH_MAX]; struct stat statbuf; if (!file || !resource) return false; snprintf(res_path, sizeof(res_path), "%s/%s/%s", INFO_PATH, resource, file); if (stat(res_path, &statbuf)) return false; return true; } bool test_resource_feature_check(const struct resctrl_test *test) { return resctrl_resource_exists(test->resource); } int filter_dmesg(void) { char line[1024]; FILE *fp; int pipefds[2]; pid_t pid; int ret; ret = pipe(pipefds); if (ret) { ksft_perror("pipe"); return ret; } fflush(stdout); pid = fork(); if (pid == 0) { close(pipefds[0]); dup2(pipefds[1], STDOUT_FILENO); execlp("dmesg", "dmesg", NULL); ksft_perror("Executing dmesg"); exit(1); } close(pipefds[1]); fp = fdopen(pipefds[0], "r"); if (!fp) { ksft_perror("fdopen(pipe)"); kill(pid, SIGTERM); return -1; } while (fgets(line, 1024, fp)) { if (strstr(line, "intel_rdt:")) ksft_print_msg("dmesg: %s", line); if (strstr(line, "resctrl:")) ksft_print_msg("dmesg: %s", line); } fclose(fp); waitpid(pid, NULL, 0); return 0; } int validate_bw_report_request(char *bw_report) { if (strcmp(bw_report, "reads") == 0) return 0; if (strcmp(bw_report, "writes") == 0) return 0; if (strcmp(bw_report, "nt-writes") == 0) { strcpy(bw_report, "writes"); return 0; } if (strcmp(bw_report, "total") == 0) return 0; fprintf(stderr, "Requested iMC B/W report type unavailable\n"); return -1; } int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags) { int ret; ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags); return ret; } unsigned int count_bits(unsigned long n) { unsigned int count = 0; while (n) { count += n & 1; n >>= 1; } return count; }