1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2026 Oxide Computer Company 14 */ 15 16 /* 17 * This test goes through and converts data files that are in a semi-custom hex 18 * format that represent DIMMs into binary data and then tries to parse the SPD 19 * data. It then looks at specific fields and flags from within them. Each 20 * module is expected to be parsed error free. 21 * 22 * Tests are organized in files around the DDR module type generation, e.g. 23 * DDR3, DDR4, and DDR5 are all found in different directories. SPD information 24 * that we use has been taken from a combination of dumping data from actual 25 * modules and transforming them, transforming tables that are distributed by 26 * vendors in datasheets and supplemental, and manually creating SPD 27 * information based on information in datasheets. In particular, LPDDR in its 28 * solder package does not actually include SPD information directly and 29 * therefore we have translated it. This is what Intel and AMD have recommended 30 * and do for their LPDDR bootstrapping. 31 */ 32 33 #include <stdlib.h> 34 #include <stdio.h> 35 #include <err.h> 36 #include <strings.h> 37 #include <errno.h> 38 #include <sys/sysmacros.h> 39 #include <libnvpair.h> 40 #include <libjedec.h> 41 #include <stdbool.h> 42 #include <limits.h> 43 #include <sys/debug.h> 44 45 #include "libjedec_hex2spd.h" 46 47 /* 48 * Default directory for data. 49 */ 50 #define SPD_DATA_DIR "/opt/util-tests/tests/hex2spd" 51 52 /* 53 * Maximum size we'll tolerate for a file. This corresponds to the largest DDR5 54 * SPD. 55 */ 56 #define SPD_MAX 2048 57 58 static const hex2spd_test_t *hex2spd_tests[] = { 59 µn_ddr4_rdimm, 60 &samsung_ddr4_lrdimm, 61 &advantech_ddr4_sodimm, 62 &advantech_ddr4_udimm, 63 µn_ddr5_rdimm, 64 &advantech_ddr5_rdimm, 65 µn_lp4, 66 &nanya_lp3, 67 µn_lp5, 68 &fake_lp5_camm2, 69 &samsung_ddr3_rdimm, 70 µn_ddr3_lrdimm, 71 &fake_lp5_socamm2 72 }; 73 74 /* 75 * Logic to convert an ASCII file with Hex to SPD data. Each byte of data is 76 * expected to be 2 hex digits conventionally arranged 16 bytes across. At any 77 * point we encounter a '#' character, we treat that as a comment and ignore the 78 * rest of the line. A line will start with an address followed by a ':'. 79 */ 80 static void * 81 hex2spd(const char *path, uint32_t *lenp) 82 { 83 char *buf = NULL; 84 size_t buflen = 0; 85 uint8_t *out = malloc(SPD_MAX); 86 uint32_t outlen = 0, curline = 0; 87 FILE *f; 88 89 f = fopen(path, "r"); 90 if (f == NULL) { 91 warnx("INTERNAL TEST ERROR: failed to find test file %s", 92 path); 93 free(out); 94 return (NULL); 95 } 96 97 if (out == NULL) { 98 err(EXIT_FAILURE, "failed to allocate %u bytes for buffer", 99 SPD_MAX); 100 } 101 102 while (getline(&buf, &buflen, f) != -1) { 103 char *comment, *colon; 104 unsigned long dataoff; 105 106 curline++; 107 108 if ((comment = strchr(buf, '#')) != NULL) { 109 *comment = '\0'; 110 } 111 112 if (*buf == '\0') 113 continue; 114 115 /* 116 * First grab out a line offset marker. This should be in order, 117 * but future us may be end up wanting to skip lots of zeros of 118 * course. 119 */ 120 errno = 0; 121 dataoff = strtoul(buf, &colon, 16); 122 if (errno != 0 || *colon != ':' || *(colon + 1) != ' ') { 123 errx(EXIT_FAILURE, "failed to parse address part of " 124 "line %u", curline); 125 } 126 127 if (dataoff >= SPD_MAX || dataoff % 0x10 != 0) { 128 errx(EXIT_FAILURE, "line %u parsed data offset %lu is " 129 "invalid", curline, dataoff); 130 } 131 132 /* 133 * We've got the starting data offset. Now go ahead and parse 134 * all the actual data that's in here. We use the max power way. 135 */ 136 if (sscanf(colon + 2, "%02x %02x %02x %02x %02x %02x %02x %02x " 137 "%02x %02x %02x %02x %02x %02x %02x %02x", 138 &out[dataoff + 0], &out[dataoff + 1], &out[dataoff + 2], 139 &out[dataoff + 3], &out[dataoff + 4], &out[dataoff + 5], 140 &out[dataoff + 6], &out[dataoff + 7], &out[dataoff + 8], 141 &out[dataoff + 9], &out[dataoff + 10], &out[dataoff + 11], 142 &out[dataoff + 12], &out[dataoff + 13], &out[dataoff + 14], 143 &out[dataoff + 15]) != 16) { 144 errx(EXIT_FAILURE, "failed to parse data from line %u", 145 curline); 146 } 147 148 outlen = MAX(outlen, dataoff + 16); 149 } 150 151 *lenp = outlen; 152 VERIFY0(fclose(f)); 153 return (out); 154 } 155 156 static bool 157 hex2spd_test_one(const char *dir, const hex2spd_test_t *test) 158 { 159 char path[PATH_MAX]; 160 void *data; 161 uint32_t dlen; 162 nvlist_t *nvl; 163 spd_error_t spd_err; 164 bool ret = true; 165 166 if (snprintf(path, sizeof (path), "%s/%s.spd", dir, test->ht_file) >= 167 sizeof (path)) { 168 errx(EXIT_FAILURE, "INTERNAL TEST ERROR: constructing test " 169 "path for %s would have overflowed internal buffer", 170 test->ht_file); 171 } 172 173 data = hex2spd(path, &dlen); 174 if (data == NULL) { 175 return (false); 176 } 177 178 nvl = libjedec_spd(data, dlen, &spd_err); 179 free(data); 180 if (spd_err != LIBJEDEC_SPD_OK) { 181 warnx("TEST FAILURE: failed to parse %s: 0x%x", path, spd_err); 182 return (false); 183 } 184 (void) printf("TEST PASSED: initially parsed %s\n", test->ht_file); 185 186 /* 187 * Verify there are no errors in this data. This means that we shouldn't 188 * find the errors key or the incomplete key. 189 */ 190 if (nvlist_exists(nvl, SPD_KEY_ERRS)) { 191 warnx("TEST FAILED: %s contains errors:", test->ht_file); 192 dump_nvlist(nvl, 0); 193 ret = false; 194 } 195 196 if (nvlist_exists(nvl, SPD_KEY_INCOMPLETE)) { 197 ret = false; 198 warnx("TEST FAILED: %s flagged as incomplete:", test->ht_file); 199 dump_nvlist(nvl, 0); 200 } 201 202 for (const hex2spd_spd_t *spd = &test->ht_checks[0]; 203 spd->hs_key != NULL; spd++) { 204 int nvret; 205 uint_t nents; 206 uint8_t *u8a; 207 uint32_t u32, *u32a; 208 uint64_t u64, *u64a; 209 boolean_t *ba; 210 char *str; 211 bool pass; 212 213 switch (spd->hs_type) { 214 case DATA_TYPE_UINT32: 215 nvret = nvlist_lookup_uint32(nvl, spd->hs_key, &u32); 216 if (nvret != 0) { 217 warnc(nvret, "TEST FAILED: %s: failed to " 218 "lookup key %s", test->ht_file, 219 spd->hs_key); 220 ret = false; 221 } else if (u32 != spd->hs_val.hs_u32) { 222 warnx("TEST FAILED: %s: key %s: found value " 223 "0x%x, but expected 0x%x", test->ht_file, 224 spd->hs_key, u32, spd->hs_val.hs_u32); 225 ret = false; 226 } else { 227 (void) printf("TEST PASSED: %s: key %s data " 228 "matches\n", test->ht_file, spd->hs_key); 229 } 230 break; 231 case DATA_TYPE_UINT64: 232 nvret = nvlist_lookup_uint64(nvl, spd->hs_key, &u64); 233 if (nvret != 0) { 234 warnc(nvret, "TEST FAILED: %s: failed to " 235 "lookup key %s", test->ht_file, 236 spd->hs_key); 237 ret = false; 238 } else if (u64 != spd->hs_val.hs_u64) { 239 warnx("TEST FAILED: %s: key %s: found value " 240 "0x%" PRIx64 ", but expected 0x%" PRIx64, 241 test->ht_file, spd->hs_key, u64, 242 spd->hs_val.hs_u64); 243 ret = false; 244 } else { 245 (void) printf("TEST PASSED: %s: key %s data " 246 "matches\n", test->ht_file, spd->hs_key); 247 } 248 break; 249 case DATA_TYPE_STRING: 250 nvret = nvlist_lookup_string(nvl, spd->hs_key, &str); 251 if (nvret != 0) { 252 warnc(nvret, "TEST FAILED: %s: failed to " 253 "lookup key %s", test->ht_file, 254 spd->hs_key); 255 ret = false; 256 } else if (strcmp(str, spd->hs_val.hs_str) != 0) { 257 warnx("TEST FAILED: %s: key %s: found value " 258 "%s, but expected %s", test->ht_file, 259 spd->hs_key, str, spd->hs_val.hs_str); 260 ret = false; 261 } else { 262 (void) printf("TEST PASSED: %s: key %s data " 263 "matches\n", test->ht_file, spd->hs_key); 264 } 265 break; 266 case DATA_TYPE_UINT8_ARRAY: 267 nvret = nvlist_lookup_uint8_array(nvl, spd->hs_key, 268 &u8a, &nents); 269 if (nvret != 0) { 270 warnc(nvret, "TEST FAILED: %s: failed to " 271 "lookup key %s", test->ht_file, 272 spd->hs_key); 273 ret = false; 274 break; 275 } 276 277 if (nents != spd->hs_val.hs_u8a.ha_nval) { 278 warnx("TEST FAILED: %s: key %s array has 0x%x " 279 "values, but expected 0x%x values", 280 test->ht_file, spd->hs_key, nents, 281 spd->hs_val.hs_u8a.ha_nval); 282 ret = false; 283 break; 284 } 285 286 pass = true; 287 for (uint_t i = 0; i < nents; i++) { 288 uint8_t targ = spd->hs_val.hs_u8a.ha_vals[i]; 289 if (u8a[i] != targ) { 290 warnx("TEST FAILED: %s: key %s: entry " 291 "[%u] has value 0x%x, but expected " 292 "0x%x", test->ht_file, spd->hs_key, 293 i, u8a[i], targ); 294 ret = false; 295 pass = false; 296 } 297 } 298 299 if (pass) { 300 (void) printf("TEST PASSED: %s: key %s data " 301 "matches\n", test->ht_file, spd->hs_key); 302 } 303 break; 304 case DATA_TYPE_UINT32_ARRAY: 305 nvret = nvlist_lookup_uint32_array(nvl, spd->hs_key, 306 &u32a, &nents); 307 if (nvret != 0) { 308 warnc(nvret, "TEST FAILED: %s: failed to " 309 "lookup key %s", test->ht_file, 310 spd->hs_key); 311 ret = false; 312 break; 313 } 314 315 if (nents != spd->hs_val.hs_u32a.ha_nval) { 316 warnx("TEST FAILED: %s: key %s array has 0x%x " 317 "values, but expected 0x%x values", 318 test->ht_file, spd->hs_key, nents, 319 spd->hs_val.hs_u32a.ha_nval); 320 ret = false; 321 break; 322 } 323 324 pass = true; 325 for (uint_t i = 0; i < nents; i++) { 326 uint32_t targ = spd->hs_val.hs_u32a.ha_vals[i]; 327 if (u32a[i] != targ) { 328 warnx("TEST FAILED: %s: key %s: entry " 329 "[%u] has value 0x%x, but expected " 330 "0x%x", test->ht_file, spd->hs_key, 331 i, u32a[i], targ); 332 ret = false; 333 pass = false; 334 } 335 } 336 337 if (pass) { 338 (void) printf("TEST PASSED: %s: key %s data " 339 "matches\n", test->ht_file, spd->hs_key); 340 } 341 break; 342 case DATA_TYPE_UINT64_ARRAY: 343 nvret = nvlist_lookup_uint64_array(nvl, spd->hs_key, 344 &u64a, &nents); 345 if (nvret != 0) { 346 warnc(nvret, "TEST FAILED: %s: failed to " 347 "lookup key %s", test->ht_file, 348 spd->hs_key); 349 ret = false; 350 break; 351 } 352 353 if (nents != spd->hs_val.hs_u64a.ha_nval) { 354 warnx("TEST FAILED: %s: key %s array has 0x%x " 355 "values, but expected 0x%x values", 356 test->ht_file, spd->hs_key, nents, 357 spd->hs_val.hs_u64a.ha_nval); 358 ret = false; 359 break; 360 } 361 362 pass = true; 363 for (uint_t i = 0; i < nents; i++) { 364 uint64_t targ = spd->hs_val.hs_u64a.ha_vals[i]; 365 if (u64a[i] != targ) { 366 warnx("TEST FAILED: %s: key %s: entry " 367 "[%u] has value 0x%" PRIx64 ", but " 368 "expected 0x%" PRIx64, 369 test->ht_file, spd->hs_key, i, 370 u64a[i], targ); 371 ret = false; 372 pass = false; 373 } 374 } 375 376 if (pass) { 377 (void) printf("TEST PASSED: %s: key %s data " 378 "matches\n", test->ht_file, spd->hs_key); 379 } 380 break; 381 382 case DATA_TYPE_BOOLEAN: 383 nvret = nvlist_lookup_boolean(nvl, spd->hs_key); 384 if (spd->hs_val.hs_bool) { 385 if (nvret != 0) { 386 warnc(nvret, "TEST FAILED: %s: failed " 387 "to lookup key %s", test->ht_file, 388 spd->hs_key); 389 ret = false; 390 } else { 391 (void) printf("TEST PASSED: %s: key %s " 392 "data matches\n", test->ht_file, 393 spd->hs_key); 394 } 395 } else { 396 if (nvret == 0) { 397 warnc(nvret, "TEST FAILED: %s: " 398 "successfully lookup up key %s, " 399 "but expected it not to be present", 400 test->ht_file, spd->hs_key); 401 ret = false; 402 } else if (nvret != ENOENT) { 403 warnx("TEST FAILED: %s: failed to " 404 "lookup key %s, but got %s not " 405 "ENOENT", test->ht_file, 406 spd->hs_key, 407 strerrorname_np(nvret)); 408 ret = false; 409 } else { 410 (void) printf("TEST PASSED: %s: key %s " 411 "data matches\n", test->ht_file, 412 spd->hs_key); 413 } 414 } 415 break; 416 case DATA_TYPE_BOOLEAN_ARRAY: 417 nvret = nvlist_lookup_boolean_array(nvl, spd->hs_key, 418 &ba, &nents); 419 if (nvret != 0) { 420 warnc(nvret, "TEST FAILED: %s: failed to " 421 "lookup key %s", test->ht_file, 422 spd->hs_key); 423 ret = false; 424 break; 425 } 426 427 if (nents != spd->hs_val.hs_ba.ha_nval) { 428 warnx("TEST FAILED: %s: key %s array has 0x%x " 429 "values, but expected 0x%x values", 430 test->ht_file, spd->hs_key, nents, 431 spd->hs_val.hs_u32a.ha_nval); 432 ret = false; 433 break; 434 } 435 436 pass = true; 437 for (uint_t i = 0; i < nents; i++) { 438 boolean_t targ = spd->hs_val.hs_ba.ha_vals[i]; 439 if (ba[i] != targ) { 440 warnx("TEST FAILED: %s: key %s: entry " 441 "[%u] is %s, but expected %s", 442 test->ht_file, spd->hs_key, i, 443 ba[i] ? "true" : "false", 444 targ ? "true" : "false"); 445 ret = false; 446 pass = false; 447 } 448 } 449 450 if (pass) { 451 (void) printf("TEST PASSED: %s: key %s data " 452 "matches\n", test->ht_file, spd->hs_key); 453 } 454 break; 455 default: 456 warnx("TEST FAILURE: %s: key %s has unsupported " 457 "data type 0x%x", test->ht_file, spd->hs_key, 458 spd->hs_type); 459 ret = false; 460 break; 461 } 462 } 463 464 nvlist_free(nvl); 465 return (ret); 466 } 467 468 int 469 main(void) 470 { 471 int ret = EXIT_SUCCESS; 472 const char *dir; 473 474 dir = getenv("HEX2SPD_DIR"); 475 if (dir == NULL) { 476 dir = SPD_DATA_DIR; 477 } 478 479 for (size_t i = 0; i < ARRAY_SIZE(hex2spd_tests); i++) { 480 if (!hex2spd_test_one(dir, hex2spd_tests[i])) 481 ret = EXIT_FAILURE; 482 } 483 484 if (ret == EXIT_SUCCESS) { 485 (void) printf("All tests passed successfully!\n"); 486 } 487 return (ret); 488 } 489