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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 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