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