| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Cache Allocation Technology (CAT) test |
| * |
| * Copyright (C) 2018 Intel Corporation |
| * |
| * Authors: |
| * Sai Praneeth Prakhya <sai.praneeth.prakhya@intel.com>, |
| * Fenghua Yu <fenghua.yu@intel.com> |
| */ |
| #include "resctrl.h" |
| #include <unistd.h> |
| |
| #define RESULT_FILE_NAME1 "result_cat1" |
| #define RESULT_FILE_NAME2 "result_cat2" |
| #define NUM_OF_RUNS 5 |
| #define MAX_DIFF_PERCENT 4 |
| #define MAX_DIFF 1000000 |
| |
| static int count_of_bits; |
| static char cbm_mask[256]; |
| static unsigned long long_mask; |
| static unsigned long cache_size; |
| |
| /* |
| * Change schemata. Write schemata to specified |
| * con_mon grp, mon_grp in resctrl FS. |
| * Run 5 times in order to get average values. |
| */ |
| static int cat_setup(int num, ...) |
| { |
| struct resctrl_val_param *p; |
| char schemata[64]; |
| va_list param; |
| int ret = 0; |
| |
| va_start(param, num); |
| p = va_arg(param, struct resctrl_val_param *); |
| va_end(param); |
| |
| /* Run NUM_OF_RUNS times */ |
| if (p->num_of_runs >= NUM_OF_RUNS) |
| return -1; |
| |
| if (p->num_of_runs == 0) { |
| sprintf(schemata, "%lx", p->mask); |
| ret = write_schemata(p->ctrlgrp, schemata, p->cpu_no, |
| p->resctrl_val); |
| } |
| p->num_of_runs++; |
| |
| return ret; |
| } |
| |
| static int check_results(struct resctrl_val_param *param) |
| { |
| char *token_array[8], temp[512]; |
| unsigned long sum_llc_perf_miss = 0; |
| int runs = 0, no_of_bits = 0; |
| FILE *fp; |
| |
| ksft_print_msg("Checking for pass/fail\n"); |
| fp = fopen(param->filename, "r"); |
| if (!fp) { |
| perror("# Cannot open file"); |
| |
| return errno; |
| } |
| |
| while (fgets(temp, sizeof(temp), fp)) { |
| char *token = strtok(temp, ":\t"); |
| int fields = 0; |
| |
| while (token) { |
| token_array[fields++] = token; |
| token = strtok(NULL, ":\t"); |
| } |
| /* |
| * Discard the first value which is inaccurate due to monitoring |
| * setup transition phase. |
| */ |
| if (runs > 0) |
| sum_llc_perf_miss += strtoul(token_array[3], NULL, 0); |
| runs++; |
| } |
| |
| fclose(fp); |
| no_of_bits = count_bits(param->mask); |
| |
| return show_cache_info(sum_llc_perf_miss, no_of_bits, param->span / 64, |
| MAX_DIFF, MAX_DIFF_PERCENT, NUM_OF_RUNS, |
| get_vendor() == ARCH_INTEL, false); |
| } |
| |
| void cat_test_cleanup(void) |
| { |
| remove(RESULT_FILE_NAME1); |
| remove(RESULT_FILE_NAME2); |
| } |
| |
| int cat_perf_miss_val(int cpu_no, int n, char *cache_type) |
| { |
| unsigned long l_mask, l_mask_1; |
| int ret, pipefd[2], sibling_cpu_no; |
| char pipe_message; |
| pid_t bm_pid; |
| |
| cache_size = 0; |
| |
| ret = remount_resctrlfs(true); |
| if (ret) |
| return ret; |
| |
| /* Get default cbm mask for L3/L2 cache */ |
| ret = get_cbm_mask(cache_type, cbm_mask); |
| if (ret) |
| return ret; |
| |
| long_mask = strtoul(cbm_mask, NULL, 16); |
| |
| /* Get L3/L2 cache size */ |
| ret = get_cache_size(cpu_no, cache_type, &cache_size); |
| if (ret) |
| return ret; |
| ksft_print_msg("Cache size :%lu\n", cache_size); |
| |
| /* Get max number of bits from default-cabm mask */ |
| count_of_bits = count_bits(long_mask); |
| |
| if (!n) |
| n = count_of_bits / 2; |
| |
| if (n > count_of_bits - 1) { |
| ksft_print_msg("Invalid input value for no_of_bits n!\n"); |
| ksft_print_msg("Please enter value in range 1 to %d\n", |
| count_of_bits - 1); |
| return -1; |
| } |
| |
| /* Get core id from same socket for running another thread */ |
| sibling_cpu_no = get_core_sibling(cpu_no); |
| if (sibling_cpu_no < 0) |
| return -1; |
| |
| struct resctrl_val_param param = { |
| .resctrl_val = CAT_STR, |
| .cpu_no = cpu_no, |
| .mum_resctrlfs = 0, |
| .setup = cat_setup, |
| }; |
| |
| l_mask = long_mask >> n; |
| l_mask_1 = ~l_mask & long_mask; |
| |
| /* Set param values for parent thread which will be allocated bitmask |
| * with (max_bits - n) bits |
| */ |
| param.span = cache_size * (count_of_bits - n) / count_of_bits; |
| strcpy(param.ctrlgrp, "c2"); |
| strcpy(param.mongrp, "m2"); |
| strcpy(param.filename, RESULT_FILE_NAME2); |
| param.mask = l_mask; |
| param.num_of_runs = 0; |
| |
| if (pipe(pipefd)) { |
| perror("# Unable to create pipe"); |
| return errno; |
| } |
| |
| bm_pid = fork(); |
| |
| /* Set param values for child thread which will be allocated bitmask |
| * with n bits |
| */ |
| if (bm_pid == 0) { |
| param.mask = l_mask_1; |
| strcpy(param.ctrlgrp, "c1"); |
| strcpy(param.mongrp, "m1"); |
| param.span = cache_size * n / count_of_bits; |
| strcpy(param.filename, RESULT_FILE_NAME1); |
| param.num_of_runs = 0; |
| param.cpu_no = sibling_cpu_no; |
| } |
| |
| remove(param.filename); |
| |
| ret = cat_val(¶m); |
| if (ret) |
| return ret; |
| |
| ret = check_results(¶m); |
| if (ret) |
| return ret; |
| |
| if (bm_pid == 0) { |
| /* Tell parent that child is ready */ |
| close(pipefd[0]); |
| pipe_message = 1; |
| if (write(pipefd[1], &pipe_message, sizeof(pipe_message)) < |
| sizeof(pipe_message)) { |
| close(pipefd[1]); |
| perror("# failed signaling parent process"); |
| return errno; |
| } |
| |
| close(pipefd[1]); |
| while (1) |
| ; |
| } else { |
| /* Parent waits for child to be ready. */ |
| close(pipefd[1]); |
| pipe_message = 0; |
| while (pipe_message != 1) { |
| if (read(pipefd[0], &pipe_message, |
| sizeof(pipe_message)) < sizeof(pipe_message)) { |
| perror("# failed reading from child process"); |
| break; |
| } |
| } |
| close(pipefd[0]); |
| kill(bm_pid, SIGKILL); |
| } |
| |
| cat_test_cleanup(); |
| if (bm_pid) |
| umount_resctrlfs(); |
| |
| return 0; |
| } |