| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Basic resctrl file system operations | |
| * | |
| * Copyright (C) 2018 Intel Corporation | |
| * | |
| * Authors: | |
| * Sai Praneeth Prakhya <sai.praneeth.prakhya@intel.com>, | |
| * Fenghua Yu <fenghua.yu@intel.com> | |
| */ | |
| #include <fcntl.h> | |
| #include <limits.h> | |
| #include "resctrl.h" | |
| int snc_unreliable; | |
| 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; | |
| } | |
| /* | |
| * Count number of CPUs in a /sys bitmap | |
| */ | |
| static unsigned int count_sys_bitmap_bits(char *name) | |
| { | |
| FILE *fp = fopen(name, "r"); | |
| int count = 0, c; | |
| if (!fp) | |
| return 0; | |
| while ((c = fgetc(fp)) != EOF) { | |
| if (!isxdigit(c)) | |
| continue; | |
| switch (c) { | |
| case 'f': | |
| count++; | |
| fallthrough; | |
| case '7': case 'b': case 'd': case 'e': | |
| count++; | |
| fallthrough; | |
| case '3': case '5': case '6': case '9': case 'a': case 'c': | |
| count++; | |
| fallthrough; | |
| case '1': case '2': case '4': case '8': | |
| count++; | |
| break; | |
| } | |
| } | |
| fclose(fp); | |
| return count; | |
| } | |
| static bool cpus_offline_empty(void) | |
| { | |
| char offline_cpus_str[64]; | |
| FILE *fp; | |
| fp = fopen("/sys/devices/system/cpu/offline", "r"); | |
| if (!fp) { | |
| ksft_perror("Could not open /sys/devices/system/cpu/offline"); | |
| return 0; | |
| } | |
| if (fscanf(fp, "%63s", offline_cpus_str) < 0) { | |
| if (!errno) { | |
| fclose(fp); | |
| return 1; | |
| } | |
| ksft_perror("Could not read /sys/devices/system/cpu/offline"); | |
| } | |
| fclose(fp); | |
| return 0; | |
| } | |
| /* | |
| * Detect SNC by comparing #CPUs in node0 with #CPUs sharing LLC with CPU0. | |
| * If any CPUs are offline declare the detection as unreliable. | |
| */ | |
| int snc_nodes_per_l3_cache(void) | |
| { | |
| int node_cpus, cache_cpus; | |
| static int snc_mode; | |
| if (!snc_mode) { | |
| snc_mode = 1; | |
| if (!cpus_offline_empty()) { | |
| ksft_print_msg("Runtime SNC detection unreliable due to offline CPUs.\n"); | |
| ksft_print_msg("Setting SNC mode to disabled.\n"); | |
| snc_unreliable = 1; | |
| return snc_mode; | |
| } | |
| node_cpus = count_sys_bitmap_bits("/sys/devices/system/node/node0/cpumap"); | |
| cache_cpus = count_sys_bitmap_bits("/sys/devices/system/cpu/cpu0/cache/index3/shared_cpu_map"); | |
| if (!node_cpus || !cache_cpus) { | |
| ksft_print_msg("Could not determine Sub-NUMA Cluster mode.\n"); | |
| snc_unreliable = 1; | |
| return snc_mode; | |
| } | |
| snc_mode = cache_cpus / node_cpus; | |
| /* | |
| * On some platforms (e.g. Hygon), | |
| * cache_cpus < node_cpus, the calculated snc_mode is 0. | |
| * | |
| * Set snc_mode = 1 to indicate that SNC mode is not | |
| * supported on the platform. | |
| */ | |
| if (!snc_mode) | |
| snc_mode = 1; | |
| if (snc_mode > 1) | |
| ksft_print_msg("SNC-%d mode discovered.\n", snc_mode); | |
| } | |
| return snc_mode; | |
| } | |
| /* | |
| * 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, "%63s", 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; | |
| } | |
| /* | |
| * The amount of cache represented by each bit in the masks | |
| * in the schemata file is reduced by a factor equal to SNC | |
| * nodes per L3 cache. | |
| * E.g. on a SNC-2 system with a 100MB L3 cache a test that | |
| * allocates memory from its local SNC node (default behavior | |
| * without using libnuma) will only see 50 MB llc_occupancy | |
| * with a fully populated L3 mask in the schemata file. | |
| */ | |
| if (cache_num == 3) | |
| *cache_size /= snc_nodes_per_l3_cache(); | |
| 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 | |
| * | |
| * Creates a group @grp_name if it does not exist yet. If @grp_name is NULL, | |
| * it is interpreted as the root group which always results in success. | |
| * | |
| * 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; | |
| if (!grp_name) | |
| 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", (int)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) | |
| * | |
| * 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, const char *ctrlgrp, const char *mongrp) | |
| { | |
| char controlgroup[128], monitorgroup[512], monitorgroup_p[256]; | |
| char tasks[1024]; | |
| int ret = 0; | |
| if (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 monitor group and write pid into if it is used */ | |
| if (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(const 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 (ctrlgrp) | |
| 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 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; | |
| } | |
| /** | |
| * snc_kernel_support - Check for existence of mon_sub_L3_00 file that indicates | |
| * SNC resctrl support on the kernel side. | |
| * | |
| * Return: 0 if not supported, 1 if SNC is disabled or SNC discovery is | |
| * unreliable or SNC is both enabled and supported. | |
| */ | |
| int snc_kernel_support(void) | |
| { | |
| char node_path[PATH_MAX]; | |
| struct stat statbuf; | |
| int ret; | |
| ret = snc_nodes_per_l3_cache(); | |
| /* | |
| * If SNC is disabled then its kernel support isn't important. If SNC | |
| * got disabled because the discovery process was unreliable the | |
| * snc_unreliable variable was set. It can be used to verify the SNC | |
| * discovery reliability elsewhere in the selftest. | |
| */ | |
| if (ret == 1) | |
| return ret; | |
| snprintf(node_path, sizeof(node_path), "%s/%s", RESCTRL_PATH, | |
| "mon_data/mon_L3_00/mon_sub_L3_00"); | |
| if (!stat(node_path, &statbuf)) | |
| return 1; | |
| return 0; | |
| } |