Commit | Line | Data |
---|---|---|
790bf585 FY |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Cache Allocation Technology (CAT) test | |
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 "resctrl.h" | |
12 | #include <unistd.h> | |
13 | ||
205de6dd | 14 | #define RESULT_FILE_NAME "result_cat" |
790bf585 | 15 | #define NUM_OF_RUNS 5 |
790bf585 | 16 | |
790bf585 | 17 | /* |
205de6dd IJ |
18 | * Minimum difference in LLC misses between a test with n+1 bits CBM to the |
19 | * test with n bits is MIN_DIFF_PERCENT_PER_BIT * (n - 1). With e.g. 5 vs 4 | |
20 | * bits in the CBM mask, the minimum difference must be at least | |
21 | * MIN_DIFF_PERCENT_PER_BIT * (4 - 1) = 3 percent. | |
22 | * | |
23 | * The relationship between number of used CBM bits and difference in LLC | |
24 | * misses is not expected to be linear. With a small number of bits, the | |
25 | * margin is smaller than with larger number of bits. For selftest purposes, | |
26 | * however, linear approach is enough because ultimately only pass/fail | |
27 | * decision has to be made and distinction between strong and stronger | |
28 | * signal is irrelevant. | |
790bf585 | 29 | */ |
205de6dd | 30 | #define MIN_DIFF_PERCENT_PER_BIT 1UL |
790bf585 | 31 | |
33403bc7 | 32 | static int show_results_info(__u64 sum_llc_val, int no_of_bits, |
205de6dd IJ |
33 | unsigned long cache_span, |
34 | unsigned long min_diff_percent, | |
35 | unsigned long num_of_runs, bool platform, | |
36 | __s64 *prev_avg_llc_val) | |
5caf1b64 | 37 | { |
33403bc7 | 38 | __u64 avg_llc_val = 0; |
205de6dd IJ |
39 | float avg_diff; |
40 | int ret = 0; | |
5caf1b64 IJ |
41 | |
42 | avg_llc_val = sum_llc_val / num_of_runs; | |
205de6dd IJ |
43 | if (*prev_avg_llc_val) { |
44 | float delta = (__s64)(avg_llc_val - *prev_avg_llc_val); | |
5caf1b64 | 45 | |
205de6dd IJ |
46 | avg_diff = delta / *prev_avg_llc_val; |
47 | ret = platform && (avg_diff * 100) < (float)min_diff_percent; | |
5caf1b64 | 48 | |
205de6dd IJ |
49 | ksft_print_msg("%s Check cache miss rate changed more than %.1f%%\n", |
50 | ret ? "Fail:" : "Pass:", (float)min_diff_percent); | |
5caf1b64 | 51 | |
205de6dd IJ |
52 | ksft_print_msg("Percent diff=%.1f\n", avg_diff * 100); |
53 | } | |
54 | *prev_avg_llc_val = avg_llc_val; | |
5caf1b64 IJ |
55 | |
56 | show_cache_info(no_of_bits, avg_llc_val, cache_span, true); | |
57 | ||
58 | return ret; | |
59 | } | |
60 | ||
205de6dd IJ |
61 | /* Remove the highest bit from CBM */ |
62 | static unsigned long next_mask(unsigned long current_mask) | |
63 | { | |
64 | return current_mask & (current_mask >> 1); | |
65 | } | |
66 | ||
67 | static int check_results(struct resctrl_val_param *param, const char *cache_type, | |
68 | unsigned long cache_total_size, unsigned long full_cache_mask, | |
69 | unsigned long current_mask) | |
790bf585 FY |
70 | { |
71 | char *token_array[8], temp[512]; | |
33403bc7 | 72 | __u64 sum_llc_perf_miss = 0; |
205de6dd IJ |
73 | __s64 prev_avg_llc_val = 0; |
74 | unsigned long alloc_size; | |
75 | int runs = 0; | |
76 | int fail = 0; | |
77 | int ret; | |
790bf585 FY |
78 | FILE *fp; |
79 | ||
ca2f4214 | 80 | ksft_print_msg("Checking for pass/fail\n"); |
790bf585 FY |
81 | fp = fopen(param->filename, "r"); |
82 | if (!fp) { | |
cc8ff7f5 | 83 | ksft_perror("Cannot open file"); |
790bf585 | 84 | |
c90fba60 | 85 | return -1; |
790bf585 FY |
86 | } |
87 | ||
88 | while (fgets(temp, sizeof(temp), fp)) { | |
89 | char *token = strtok(temp, ":\t"); | |
90 | int fields = 0; | |
205de6dd | 91 | int bits; |
790bf585 FY |
92 | |
93 | while (token) { | |
94 | token_array[fields++] = token; | |
95 | token = strtok(NULL, ":\t"); | |
96 | } | |
205de6dd IJ |
97 | |
98 | sum_llc_perf_miss += strtoull(token_array[3], NULL, 0); | |
790bf585 | 99 | runs++; |
205de6dd IJ |
100 | |
101 | if (runs < NUM_OF_RUNS) | |
102 | continue; | |
103 | ||
104 | if (!current_mask) { | |
105 | ksft_print_msg("Unexpected empty cache mask\n"); | |
106 | break; | |
107 | } | |
108 | ||
109 | alloc_size = cache_portion_size(cache_total_size, current_mask, full_cache_mask); | |
110 | ||
111 | bits = count_bits(current_mask); | |
112 | ||
113 | ret = show_results_info(sum_llc_perf_miss, bits, | |
114 | alloc_size / 64, | |
115 | MIN_DIFF_PERCENT_PER_BIT * (bits - 1), | |
116 | runs, get_vendor() == ARCH_INTEL, | |
117 | &prev_avg_llc_val); | |
118 | if (ret) | |
119 | fail = 1; | |
120 | ||
121 | runs = 0; | |
122 | sum_llc_perf_miss = 0; | |
123 | current_mask = next_mask(current_mask); | |
790bf585 FY |
124 | } |
125 | ||
126 | fclose(fp); | |
790bf585 | 127 | |
205de6dd | 128 | return fail; |
790bf585 FY |
129 | } |
130 | ||
131 | void cat_test_cleanup(void) | |
132 | { | |
205de6dd | 133 | remove(RESULT_FILE_NAME); |
790bf585 FY |
134 | } |
135 | ||
433f437b IJ |
136 | /* |
137 | * cat_test - Execute CAT benchmark and measure cache misses | |
138 | * @param: Parameters passed to cat_test() | |
139 | * @span: Buffer size for the benchmark | |
205de6dd IJ |
140 | * @current_mask Start mask for the first iteration |
141 | * | |
142 | * Run CAT selftest by varying the allocated cache portion and comparing the | |
143 | * impact on cache misses (the result analysis is done in check_results() | |
144 | * and show_results_info(), not in this function). | |
145 | * | |
146 | * One bit is removed from the CAT allocation bit mask (in current_mask) for | |
147 | * each subsequent test which keeps reducing the size of the allocated cache | |
148 | * portion. A single test flushes the buffer, reads it to warm up the cache, | |
149 | * and reads the buffer again. The cache misses are measured during the last | |
150 | * read pass. | |
433f437b IJ |
151 | * |
152 | * Return: 0 when the test was run, < 0 on error. | |
153 | */ | |
205de6dd | 154 | static int cat_test(struct resctrl_val_param *param, size_t span, unsigned long current_mask) |
433f437b | 155 | { |
433f437b IJ |
156 | char *resctrl_val = param->resctrl_val; |
157 | struct perf_event_read pe_read; | |
158 | struct perf_event_attr pea; | |
6c8cb747 | 159 | cpu_set_t old_affinity; |
205de6dd IJ |
160 | unsigned char *buf; |
161 | char schemata[64]; | |
162 | int ret, i, pe_fd; | |
433f437b | 163 | pid_t bm_pid; |
433f437b IJ |
164 | |
165 | if (strcmp(param->filename, "") == 0) | |
166 | sprintf(param->filename, "stdio"); | |
167 | ||
168 | bm_pid = getpid(); | |
169 | ||
170 | /* Taskset benchmark to specified cpu */ | |
6c8cb747 | 171 | ret = taskset_benchmark(bm_pid, param->cpu_no, &old_affinity); |
433f437b IJ |
172 | if (ret) |
173 | return ret; | |
174 | ||
175 | /* Write benchmark to specified con_mon grp, mon_grp in resctrl FS*/ | |
176 | ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp, | |
177 | resctrl_val); | |
178 | if (ret) | |
6c8cb747 | 179 | goto reset_affinity; |
433f437b IJ |
180 | |
181 | perf_event_attr_initialize(&pea, PERF_COUNT_HW_CACHE_MISSES); | |
182 | perf_event_initialize_read_format(&pe_read); | |
2892731e | 183 | pe_fd = perf_open(&pea, bm_pid, param->cpu_no); |
6c8cb747 IJ |
184 | if (pe_fd < 0) { |
185 | ret = -1; | |
186 | goto reset_affinity; | |
187 | } | |
433f437b | 188 | |
205de6dd IJ |
189 | buf = alloc_buffer(span, 1); |
190 | if (!buf) { | |
191 | ret = -1; | |
192 | goto pe_close; | |
193 | } | |
2892731e | 194 | |
205de6dd IJ |
195 | while (current_mask) { |
196 | snprintf(schemata, sizeof(schemata), "%lx", param->mask & ~current_mask); | |
197 | ret = write_schemata("", schemata, param->cpu_no, param->resctrl_val); | |
2892731e | 198 | if (ret) |
205de6dd IJ |
199 | goto free_buf; |
200 | snprintf(schemata, sizeof(schemata), "%lx", current_mask); | |
201 | ret = write_schemata(param->ctrlgrp, schemata, param->cpu_no, param->resctrl_val); | |
202 | if (ret) | |
203 | goto free_buf; | |
433f437b | 204 | |
205de6dd IJ |
205 | for (i = 0; i < NUM_OF_RUNS; i++) { |
206 | mem_flush(buf, span); | |
207 | fill_cache_read(buf, span, true); | |
433f437b | 208 | |
205de6dd IJ |
209 | ret = perf_event_reset_enable(pe_fd); |
210 | if (ret) | |
211 | goto free_buf; | |
433f437b | 212 | |
205de6dd | 213 | fill_cache_read(buf, span, true); |
433f437b | 214 | |
205de6dd IJ |
215 | ret = perf_event_measure(pe_fd, &pe_read, param->filename, bm_pid); |
216 | if (ret) | |
217 | goto free_buf; | |
218 | } | |
219 | current_mask = next_mask(current_mask); | |
220 | } | |
221 | ||
222 | free_buf: | |
223 | free(buf); | |
433f437b IJ |
224 | pe_close: |
225 | close(pe_fd); | |
6c8cb747 IJ |
226 | reset_affinity: |
227 | taskset_restore(bm_pid, &old_affinity); | |
205de6dd | 228 | |
433f437b IJ |
229 | return ret; |
230 | } | |
231 | ||
790bf585 FY |
232 | int cat_perf_miss_val(int cpu_no, int n, char *cache_type) |
233 | { | |
205de6dd | 234 | unsigned long long_mask, start_mask, full_cache_mask; |
19e94a23 | 235 | unsigned long cache_total_size = 0; |
205de6dd | 236 | unsigned int start; |
85b73447 | 237 | int count_of_bits; |
b1a901e0 | 238 | size_t span; |
205de6dd | 239 | int ret; |
790bf585 | 240 | |
b6dfac94 IJ |
241 | ret = get_full_cbm(cache_type, &full_cache_mask); |
242 | if (ret) | |
243 | return ret; | |
244 | /* Get the largest contiguous exclusive portion of the cache */ | |
245 | ret = get_mask_no_shareable(cache_type, &long_mask); | |
790bf585 FY |
246 | if (ret) |
247 | return ret; | |
248 | ||
790bf585 | 249 | /* Get L3/L2 cache size */ |
19e94a23 | 250 | ret = get_cache_size(cpu_no, cache_type, &cache_total_size); |
790bf585 FY |
251 | if (ret) |
252 | return ret; | |
19e94a23 | 253 | ksft_print_msg("Cache size :%lu\n", cache_total_size); |
790bf585 | 254 | |
205de6dd | 255 | count_of_bits = count_contiguous_bits(long_mask, &start); |
790bf585 | 256 | |
09a67934 FY |
257 | if (!n) |
258 | n = count_of_bits / 2; | |
259 | ||
260 | if (n > count_of_bits - 1) { | |
ca2f4214 FY |
261 | ksft_print_msg("Invalid input value for no_of_bits n!\n"); |
262 | ksft_print_msg("Please enter value in range 1 to %d\n", | |
263 | count_of_bits - 1); | |
790bf585 FY |
264 | return -1; |
265 | } | |
205de6dd | 266 | start_mask = create_bit_mask(start, n); |
790bf585 FY |
267 | |
268 | struct resctrl_val_param param = { | |
24286736 | 269 | .resctrl_val = CAT_STR, |
790bf585 | 270 | .cpu_no = cpu_no, |
205de6dd IJ |
271 | .ctrlgrp = "c1", |
272 | .filename = RESULT_FILE_NAME, | |
273 | .num_of_runs = 0, | |
790bf585 | 274 | }; |
205de6dd IJ |
275 | param.mask = long_mask; |
276 | span = cache_portion_size(cache_total_size, start_mask, full_cache_mask); | |
790bf585 FY |
277 | |
278 | remove(param.filename); | |
279 | ||
205de6dd IJ |
280 | ret = cat_test(¶m, span, start_mask); |
281 | if (ret) | |
282 | goto out; | |
790bf585 | 283 | |
205de6dd IJ |
284 | ret = check_results(¶m, cache_type, cache_total_size, full_cache_mask, start_mask); |
285 | out: | |
790bf585 | 286 | cat_test_cleanup(); |
790bf585 | 287 | |
39e34ddc | 288 | return ret; |
790bf585 | 289 | } |