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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 = ¶m_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 */ 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 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