1 // SPDX-License-Identifier: GPL-2.0-only
2 // Copyright(c) 2025 Intel Corporation. All rights reserved.
3
4 /* Preface all log entries with "cxl_translate" */
5 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
6
7 #include <linux/moduleparam.h>
8 #include <linux/module.h>
9 #include <linux/kernel.h>
10 #include <linux/init.h>
11 #include <linux/slab.h>
12 #include <linux/acpi.h>
13 #include <cxlmem.h>
14 #include <cxl.h>
15
16 /* Maximum number of test vectors and entry length */
17 #define MAX_TABLE_ENTRIES 128
18 #define MAX_ENTRY_LEN 128
19
20 /* Expected number of parameters in each test vector */
21 #define EXPECTED_PARAMS 7
22
23 /* Module parameters for test vectors */
24 static char *table[MAX_TABLE_ENTRIES];
25 static int table_num;
26
27 /* Interleave Arithmetic */
28 #define MODULO_MATH 0
29 #define XOR_MATH 1
30
31 /*
32 * XOR mapping configuration
33 * The test data sets all use the same set of xormaps. When additional
34 * data sets arrive for validation, this static setup will need to
35 * be changed to accept xormaps as additional parameters.
36 */
37 struct cxl_cxims_data *cximsd;
38 static u64 xormaps[] = {
39 0x2020900,
40 0x4041200,
41 0x1010400,
42 0x800,
43 };
44
45 static int nr_maps = ARRAY_SIZE(xormaps);
46
47 #define HBIW_TO_NR_MAPS_SIZE (CXL_DECODER_MAX_INTERLEAVE + 1)
48 static const int hbiw_to_nr_maps[HBIW_TO_NR_MAPS_SIZE] = {
49 [1] = 0, [2] = 1, [3] = 0, [4] = 2, [6] = 1, [8] = 3, [12] = 2, [16] = 4
50 };
51
52 /**
53 * to_hpa - calculate an HPA offset from a DPA offset and position
54 *
55 * dpa_offset: device physical address offset
56 * pos: devices position in interleave
57 * r_eiw: region encoded interleave ways
58 * r_eig: region encoded interleave granularity
59 * hb_ways: host bridge interleave ways
60 * math: interleave arithmetic (MODULO_MATH or XOR_MATH)
61 *
62 * Returns: host physical address offset
63 */
to_hpa(u64 dpa_offset,int pos,u8 r_eiw,u16 r_eig,u8 hb_ways,u8 math)64 static u64 to_hpa(u64 dpa_offset, int pos, u8 r_eiw, u16 r_eig, u8 hb_ways,
65 u8 math)
66 {
67 u64 hpa_offset;
68
69 /* Calculate base HPA offset from DPA and position */
70 hpa_offset = cxl_calculate_hpa_offset(dpa_offset, pos, r_eiw, r_eig);
71
72 if (math == XOR_MATH) {
73 cximsd->nr_maps = hbiw_to_nr_maps[hb_ways];
74 if (cximsd->nr_maps)
75 return cxl_do_xormap_calc(cximsd, hpa_offset, hb_ways);
76 }
77 return hpa_offset;
78 }
79
80 /**
81 * to_dpa - translate an HPA offset to DPA offset
82 *
83 * hpa_offset: host physical address offset
84 * r_eiw: region encoded interleave ways
85 * r_eig: region encoded interleave granularity
86 * hb_ways: host bridge interleave ways
87 * math: interleave arithmetic (MODULO_MATH or XOR_MATH)
88 *
89 * Returns: device physical address offset
90 */
to_dpa(u64 hpa_offset,u8 r_eiw,u16 r_eig,u8 hb_ways,u8 math)91 static u64 to_dpa(u64 hpa_offset, u8 r_eiw, u16 r_eig, u8 hb_ways, u8 math)
92 {
93 u64 offset = hpa_offset;
94
95 if (math == XOR_MATH) {
96 cximsd->nr_maps = hbiw_to_nr_maps[hb_ways];
97 if (cximsd->nr_maps)
98 offset =
99 cxl_do_xormap_calc(cximsd, hpa_offset, hb_ways);
100 }
101 return cxl_calculate_dpa_offset(offset, r_eiw, r_eig);
102 }
103
104 /**
105 * to_pos - extract an interleave position from an HPA offset
106 *
107 * hpa_offset: host physical address offset
108 * r_eiw: region encoded interleave ways
109 * r_eig: region encoded interleave granularity
110 * hb_ways: host bridge interleave ways
111 * math: interleave arithmetic (MODULO_MATH or XOR_MATH)
112 *
113 * Returns: devices position in region interleave
114 */
to_pos(u64 hpa_offset,u8 r_eiw,u16 r_eig,u8 hb_ways,u8 math)115 static u64 to_pos(u64 hpa_offset, u8 r_eiw, u16 r_eig, u8 hb_ways, u8 math)
116 {
117 u64 offset = hpa_offset;
118
119 /* Reverse XOR mapping if specified */
120 if (math == XOR_MATH)
121 offset = cxl_do_xormap_calc(cximsd, hpa_offset, hb_ways);
122
123 return cxl_calculate_position(offset, r_eiw, r_eig);
124 }
125
126 /**
127 * run_translation_test - execute forward and reverse translations
128 *
129 * @dpa: device physical address
130 * @pos: expected position in region interleave
131 * @r_eiw: region encoded interleave ways
132 * @r_eig: region encoded interleave granularity
133 * @hb_ways: host bridge interleave ways
134 * @math: interleave arithmetic (MODULO_MATH or XOR_MATH)
135 * @expect_spa: expected system physical address
136 *
137 * Returns: 0 on success, -1 on failure
138 */
run_translation_test(u64 dpa,int pos,u8 r_eiw,u16 r_eig,u8 hb_ways,int math,u64 expect_hpa)139 static int run_translation_test(u64 dpa, int pos, u8 r_eiw, u16 r_eig,
140 u8 hb_ways, int math, u64 expect_hpa)
141 {
142 u64 translated_spa, reverse_dpa;
143 int reverse_pos;
144
145 /* Test Device to Host translation: DPA + POS -> SPA */
146 translated_spa = to_hpa(dpa, pos, r_eiw, r_eig, hb_ways, math);
147 if (translated_spa != expect_hpa) {
148 pr_err("Device to host failed: expected HPA %llu, got %llu\n",
149 expect_hpa, translated_spa);
150 return -1;
151 }
152
153 /* Test Host to Device DPA translation: SPA -> DPA */
154 reverse_dpa = to_dpa(translated_spa, r_eiw, r_eig, hb_ways, math);
155 if (reverse_dpa != dpa) {
156 pr_err("Host to Device DPA failed: expected %llu, got %llu\n",
157 dpa, reverse_dpa);
158 return -1;
159 }
160
161 /* Test Host to Device Position translation: SPA -> POS */
162 reverse_pos = to_pos(translated_spa, r_eiw, r_eig, hb_ways, math);
163 if (reverse_pos != pos) {
164 pr_err("Position lookup failed: expected %d, got %d\n", pos,
165 reverse_pos);
166 return -1;
167 }
168
169 return 0;
170 }
171
172 /**
173 * parse_test_vector - parse a single test vector string
174 *
175 * entry: test vector string to parse
176 * dpa: device physical address
177 * pos: expected position in region interleave
178 * r_eiw: region encoded interleave ways
179 * r_eig: region encoded interleave granularity
180 * hb_ways: host bridge interleave ways
181 * math: interleave arithmetic (MODULO_MATH or XOR_MATH)
182 * expect_spa: expected system physical address
183 *
184 * Returns: 0 on success, negative error code on failure
185 */
parse_test_vector(const char * entry,u64 * dpa,int * pos,u8 * r_eiw,u16 * r_eig,u8 * hb_ways,int * math,u64 * expect_hpa)186 static int parse_test_vector(const char *entry, u64 *dpa, int *pos, u8 *r_eiw,
187 u16 *r_eig, u8 *hb_ways, int *math,
188 u64 *expect_hpa)
189 {
190 unsigned int tmp_r_eiw, tmp_r_eig, tmp_hb_ways;
191 int parsed;
192
193 parsed = sscanf(entry, "%llu %d %u %u %u %d %llu", dpa, pos, &tmp_r_eiw,
194 &tmp_r_eig, &tmp_hb_ways, math, expect_hpa);
195
196 if (parsed != EXPECTED_PARAMS) {
197 pr_err("Parse error: expected %d parameters, got %d in '%s'\n",
198 EXPECTED_PARAMS, parsed, entry);
199 return -EINVAL;
200 }
201 if (tmp_r_eiw > U8_MAX || tmp_r_eig > U16_MAX || tmp_hb_ways > U8_MAX) {
202 pr_err("Parameter overflow in entry: '%s'\n", entry);
203 return -ERANGE;
204 }
205 if (*math != MODULO_MATH && *math != XOR_MATH) {
206 pr_err("Invalid math type %d in entry: '%s'\n", *math, entry);
207 return -EINVAL;
208 }
209 *r_eiw = tmp_r_eiw;
210 *r_eig = tmp_r_eig;
211 *hb_ways = tmp_hb_ways;
212
213 return 0;
214 }
215
216 /*
217 * setup_xor_mapping - Initialize XOR mapping data structure
218 *
219 * The test data sets all use the same HBIG so we can use one set
220 * of xormaps, and set the number to apply based on HBIW before
221 * calling cxl_do_xormap_calc().
222 *
223 * When additional data sets arrive for validation with different
224 * HBIG's this static setup will need to be updated.
225 *
226 * Returns: 0 on success, negative error code on failure
227 */
setup_xor_mapping(void)228 static int setup_xor_mapping(void)
229 {
230 if (nr_maps <= 0)
231 return -EINVAL;
232
233 cximsd = kzalloc(struct_size(cximsd, xormaps, nr_maps), GFP_KERNEL);
234 if (!cximsd)
235 return -ENOMEM;
236
237 memcpy(cximsd->xormaps, xormaps, nr_maps * sizeof(*cximsd->xormaps));
238 cximsd->nr_maps = nr_maps;
239
240 return 0;
241 }
242
test_random_params(void)243 static int test_random_params(void)
244 {
245 u8 valid_eiws[] = { 0, 1, 2, 3, 4, 8, 9, 10 };
246 u16 valid_eigs[] = { 0, 1, 2, 3, 4, 5, 6 };
247 int i, ways, pos, reverse_pos;
248 u64 dpa, hpa, reverse_dpa;
249 int iterations = 10000;
250 int failures = 0;
251
252 for (i = 0; i < iterations; i++) {
253 /* Generate valid random parameters for eiw, eig, pos, dpa */
254 u8 eiw = valid_eiws[get_random_u32() % ARRAY_SIZE(valid_eiws)];
255 u16 eig = valid_eigs[get_random_u32() % ARRAY_SIZE(valid_eigs)];
256
257 eiw_to_ways(eiw, &ways);
258 pos = get_random_u32() % ways;
259 dpa = get_random_u64() >> 12;
260
261 hpa = cxl_calculate_hpa_offset(dpa, pos, eiw, eig);
262 reverse_dpa = cxl_calculate_dpa_offset(hpa, eiw, eig);
263 reverse_pos = cxl_calculate_position(hpa, eiw, eig);
264
265 if (reverse_dpa != dpa || reverse_pos != pos) {
266 pr_err("test random iter %d FAIL hpa=%llu, dpa=%llu reverse_dpa=%llu, pos=%d reverse_pos=%d eiw=%u eig=%u\n",
267 i, hpa, dpa, reverse_dpa, pos, reverse_pos, eiw,
268 eig);
269
270 if (failures++ > 10) {
271 pr_err("test random too many failures, stop\n");
272 break;
273 }
274 }
275 }
276 pr_info("..... test random: PASS %d FAIL %d\n", i - failures, failures);
277
278 if (failures)
279 return -EINVAL;
280
281 return 0;
282 }
283
284 struct param_test {
285 u8 eiw;
286 u16 eig;
287 int pos;
288 bool expect; /* true: expect pass, false: expect fail */
289 const char *desc;
290 };
291
292 static struct param_test param_tests[] = {
293 { 0x0, 0, 0, true, "1-way, min eig=0, pos=0" },
294 { 0x0, 3, 0, true, "1-way, mid eig=3, pos=0" },
295 { 0x0, 6, 0, true, "1-way, max eig=6, pos=0" },
296 { 0x1, 0, 0, true, "2-way, eig=0, pos=0" },
297 { 0x1, 3, 1, true, "2-way, eig=3, max pos=1" },
298 { 0x1, 6, 1, true, "2-way, eig=6, max pos=1" },
299 { 0x2, 0, 0, true, "4-way, eig=0, pos=0" },
300 { 0x2, 3, 3, true, "4-way, eig=3, max pos=3" },
301 { 0x2, 6, 3, true, "4-way, eig=6, max pos=3" },
302 { 0x3, 0, 0, true, "8-way, eig=0, pos=0" },
303 { 0x3, 3, 7, true, "8-way, eig=3, max pos=7" },
304 { 0x3, 6, 7, true, "8-way, eig=6, max pos=7" },
305 { 0x4, 0, 0, true, "16-way, eig=0, pos=0" },
306 { 0x4, 3, 15, true, "16-way, eig=3, max pos=15" },
307 { 0x4, 6, 15, true, "16-way, eig=6, max pos=15" },
308 { 0x8, 0, 0, true, "3-way, eig=0, pos=0" },
309 { 0x8, 3, 2, true, "3-way, eig=3, max pos=2" },
310 { 0x8, 6, 2, true, "3-way, eig=6, max pos=2" },
311 { 0x9, 0, 0, true, "6-way, eig=0, pos=0" },
312 { 0x9, 3, 5, true, "6-way, eig=3, max pos=5" },
313 { 0x9, 6, 5, true, "6-way, eig=6, max pos=5" },
314 { 0xA, 0, 0, true, "12-way, eig=0, pos=0" },
315 { 0xA, 3, 11, true, "12-way, eig=3, max pos=11" },
316 { 0xA, 6, 11, true, "12-way, eig=6, max pos=11" },
317 { 0x5, 0, 0, false, "invalid eiw=5" },
318 { 0x7, 0, 0, false, "invalid eiw=7" },
319 { 0xB, 0, 0, false, "invalid eiw=0xB" },
320 { 0xFF, 0, 0, false, "invalid eiw=0xFF" },
321 { 0x1, 7, 0, false, "invalid eig=7 (out of range)" },
322 { 0x2, 0x10, 0, false, "invalid eig=0x10" },
323 { 0x3, 0xFFFF, 0, false, "invalid eig=0xFFFF" },
324 { 0x1, 0, -1, false, "pos < 0" },
325 { 0x1, 0, 2, false, "2-way, pos=2 (>= ways)" },
326 { 0x2, 0, 4, false, "4-way, pos=4 (>= ways)" },
327 { 0x3, 0, 8, false, "8-way, pos=8 (>= ways)" },
328 { 0x4, 0, 16, false, "16-way, pos=16 (>= ways)" },
329 { 0x8, 0, 3, false, "3-way, pos=3 (>= ways)" },
330 { 0x9, 0, 6, false, "6-way, pos=6 (>= ways)" },
331 { 0xA, 0, 12, false, "12-way, pos=12 (>= ways)" },
332 };
333
test_cxl_validate_translation_params(void)334 static int test_cxl_validate_translation_params(void)
335 {
336 int i, rc, failures = 0;
337 bool valid;
338
339 for (i = 0; i < ARRAY_SIZE(param_tests); i++) {
340 struct param_test *t = ¶m_tests[i];
341
342 rc = cxl_validate_translation_params(t->eiw, t->eig, t->pos);
343 valid = (rc == 0);
344
345 if (valid != t->expect) {
346 pr_err("test params failed: %s\n", t->desc);
347 failures++;
348 }
349 }
350 pr_info("..... test params: PASS %d FAIL %d\n", i - failures, failures);
351
352 if (failures)
353 return -EINVAL;
354
355 return 0;
356 }
357
358 /*
359 * cxl_translate_init
360 *
361 * Run the internal validation tests when no params are passed.
362 * Otherwise, parse the parameters (test vectors), and kick off
363 * the translation test.
364 *
365 * Returns: 0 on success, negative error code on failure
366 */
cxl_translate_init(void)367 static int __init cxl_translate_init(void)
368 {
369 int rc, i;
370
371 /* If no tables are passed, validate module params only */
372 if (table_num == 0) {
373 pr_info("Internal validation test start...\n");
374 rc = test_cxl_validate_translation_params();
375 if (rc)
376 return rc;
377
378 rc = test_random_params();
379 if (rc)
380 return rc;
381
382 pr_info("Internal validation test completed successfully\n");
383
384 return 0;
385 }
386
387 pr_info("CXL translate test module loaded with %d test vectors\n",
388 table_num);
389
390 rc = setup_xor_mapping();
391 if (rc)
392 return rc;
393
394 /* Process each test vector */
395 for (i = 0; i < table_num; i++) {
396 u64 dpa, expect_spa;
397 int pos, math;
398 u8 r_eiw, hb_ways;
399 u16 r_eig;
400
401 pr_debug("Processing test vector %d: '%s'\n", i, table[i]);
402
403 /* Parse the test vector */
404 rc = parse_test_vector(table[i], &dpa, &pos, &r_eiw, &r_eig,
405 &hb_ways, &math, &expect_spa);
406 if (rc) {
407 pr_err("CXL Translate Test %d: FAIL\n"
408 " Failed to parse test vector '%s'\n",
409 i, table[i]);
410 continue;
411 }
412 /* Run the translation test */
413 rc = run_translation_test(dpa, pos, r_eiw, r_eig, hb_ways, math,
414 expect_spa);
415 if (rc) {
416 pr_err("CXL Translate Test %d: FAIL\n"
417 " dpa=%llu pos=%d r_eiw=%u r_eig=%u hb_ways=%u math=%s expect_spa=%llu\n",
418 i, dpa, pos, r_eiw, r_eig, hb_ways,
419 (math == XOR_MATH) ? "XOR" : "MODULO",
420 expect_spa);
421 } else {
422 pr_info("CXL Translate Test %d: PASS\n", i);
423 }
424 }
425
426 kfree(cximsd);
427 pr_info("CXL translate test completed\n");
428
429 return 0;
430 }
431
cxl_translate_exit(void)432 static void __exit cxl_translate_exit(void)
433 {
434 pr_info("CXL translate test module unloaded\n");
435 }
436
437 module_param_array(table, charp, &table_num, 0444);
438 MODULE_PARM_DESC(table, "Test vectors as space-separated decimal strings");
439
440 MODULE_LICENSE("GPL");
441 MODULE_DESCRIPTION("cxl_test: cxl address translation test module");
442 MODULE_IMPORT_NS("CXL");
443
444 module_init(cxl_translate_init);
445 module_exit(cxl_translate_exit);
446