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 2024 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 }; 72 73 /* 74 * Logic to convert an ASCII file with Hex to SPD data. Each byte of data is 75 * expected to be 2 hex digits conventionally arranged 16 bytes across. At any 76 * point we encounter a '#' character, we treat that as a comment and ignore the 77 * rest of the line. A line will start with an address followed by a ':'. 78 */ 79 static void * 80 hex2spd(const char *path, uint32_t *lenp) 81 { 82 char *buf = NULL; 83 size_t buflen = 0; 84 uint8_t *out = malloc(SPD_MAX); 85 uint32_t outlen = 0, curline = 0; 86 FILE *f; 87 88 f = fopen(path, "r"); 89 if (f == NULL) { 90 warnx("INTERNAL TEST ERROR: failed to find test file %s", 91 path); 92 free(out); 93 return (NULL); 94 } 95 96 if (out == NULL) { 97 err(EXIT_FAILURE, "failed to allocate %u bytes for buffer", 98 SPD_MAX); 99 } 100 101 while (getline(&buf, &buflen, f) != -1) { 102 char *comment, *colon; 103 unsigned long dataoff; 104 105 curline++; 106 107 if ((comment = strchr(buf, '#')) != NULL) { 108 *comment = '\0'; 109 } 110 111 if (*buf == '\0') 112 continue; 113 114 /* 115 * First grab out a line offset marker. This should be in order, 116 * but future us may be end up wanting to skip lots of zeros of 117 * course. 118 */ 119 errno = 0; 120 dataoff = strtoul(buf, &colon, 16); 121 if (errno != 0 || *colon != ':' || *(colon + 1) != ' ') { 122 errx(EXIT_FAILURE, "failed to parse address part of " 123 "line %u", curline); 124 } 125 126 if (dataoff >= SPD_MAX || dataoff % 0x10 != 0) { 127 errx(EXIT_FAILURE, "line %u parsed data offset %lu is " 128 "invalid", curline, dataoff); 129 } 130 131 /* 132 * We've got the starting data offset. Now go ahead and parse 133 * all the actual data that's in here. We use the max power way. 134 */ 135 if (sscanf(colon + 2, "%02x %02x %02x %02x %02x %02x %02x %02x " 136 "%02x %02x %02x %02x %02x %02x %02x %02x", 137 &out[dataoff + 0], &out[dataoff + 1], &out[dataoff + 2], 138 &out[dataoff + 3], &out[dataoff + 4], &out[dataoff + 5], 139 &out[dataoff + 6], &out[dataoff + 7], &out[dataoff + 8], 140 &out[dataoff + 9], &out[dataoff + 10], &out[dataoff + 11], 141 &out[dataoff + 12], &out[dataoff + 13], &out[dataoff + 14], 142 &out[dataoff + 15]) != 16) { 143 errx(EXIT_FAILURE, "failed to parse data from line %u", 144 curline); 145 } 146 147 outlen = MAX(outlen, dataoff + 16); 148 } 149 150 *lenp = outlen; 151 VERIFY0(fclose(f)); 152 return (out); 153 } 154 155 static bool 156 hex2spd_test_one(const char *dir, const hex2spd_test_t *test) 157 { 158 char path[PATH_MAX]; 159 void *data; 160 uint32_t dlen; 161 nvlist_t *nvl; 162 spd_error_t spd_err; 163 bool ret = true; 164 165 if (snprintf(path, sizeof (path), "%s/%s.spd", dir, test->ht_file) >= 166 sizeof (path)) { 167 errx(EXIT_FAILURE, "INTERNAL TEST ERROR: constructing test " 168 "path for %s would have overflowed internal buffer", 169 test->ht_file); 170 } 171 172 data = hex2spd(path, &dlen); 173 if (data == NULL) { 174 return (false); 175 } 176 177 nvl = libjedec_spd(data, dlen, &spd_err); 178 free(data); 179 if (spd_err != LIBJEDEC_SPD_OK) { 180 warnx("TEST FAILURE: failed to parse %s: 0x%x", path, spd_err); 181 return (false); 182 } 183 (void) printf("TEST PASSED: initially parsed %s\n", test->ht_file); 184 185 /* 186 * Verify there are no errors in this data. This means that we shouldn't 187 * find the errors key or the incomplete key. 188 */ 189 if (nvlist_exists(nvl, SPD_KEY_ERRS)) { 190 warnx("TEST FAILED: %s contains errors:", test->ht_file); 191 dump_nvlist(nvl, 0); 192 ret = false; 193 } 194 195 if (nvlist_exists(nvl, SPD_KEY_INCOMPLETE)) { 196 ret = false; 197 warnx("TEST FAILED: %s flagged as incomplete:", test->ht_file); 198 dump_nvlist(nvl, 0); 199 } 200 201 for (const hex2spd_spd_t *spd = &test->ht_checks[0]; 202 spd->hs_key != NULL; spd++) { 203 int nvret; 204 uint_t nents; 205 uint8_t *u8a; 206 uint32_t u32, *u32a; 207 uint64_t u64, *u64a; 208 boolean_t *ba; 209 char *str; 210 bool pass; 211 212 switch (spd->hs_type) { 213 case DATA_TYPE_UINT32: 214 nvret = nvlist_lookup_uint32(nvl, spd->hs_key, &u32); 215 if (nvret != 0) { 216 warnc(nvret, "TEST FAILED: %s: failed to " 217 "lookup key %s", test->ht_file, 218 spd->hs_key); 219 ret = false; 220 } else if (u32 != spd->hs_val.hs_u32) { 221 warnx("TEST FAILED: %s: key %s: found value " 222 "0x%x, but expected 0x%x", test->ht_file, 223 spd->hs_key, u32, spd->hs_val.hs_u32); 224 ret = false; 225 } else { 226 (void) printf("TEST PASSED: %s: key %s data " 227 "matches\n", test->ht_file, spd->hs_key); 228 } 229 break; 230 case DATA_TYPE_UINT64: 231 nvret = nvlist_lookup_uint64(nvl, spd->hs_key, &u64); 232 if (nvret != 0) { 233 warnc(nvret, "TEST FAILED: %s: failed to " 234 "lookup key %s", test->ht_file, 235 spd->hs_key); 236 ret = false; 237 } else if (u64 != spd->hs_val.hs_u64) { 238 warnx("TEST FAILED: %s: key %s: found value " 239 "0x%" PRIx64 ", but expected 0x%" PRIx64, 240 test->ht_file, spd->hs_key, u64, 241 spd->hs_val.hs_u64); 242 ret = false; 243 } else { 244 (void) printf("TEST PASSED: %s: key %s data " 245 "matches\n", test->ht_file, spd->hs_key); 246 } 247 break; 248 case DATA_TYPE_STRING: 249 nvret = nvlist_lookup_string(nvl, spd->hs_key, &str); 250 if (nvret != 0) { 251 warnc(nvret, "TEST FAILED: %s: failed to " 252 "lookup key %s", test->ht_file, 253 spd->hs_key); 254 ret = false; 255 } else if (strcmp(str, spd->hs_val.hs_str) != 0) { 256 warnx("TEST FAILED: %s: key %s: found value " 257 "%s, but expected %s", test->ht_file, 258 spd->hs_key, str, spd->hs_val.hs_str); 259 ret = false; 260 } else { 261 (void) printf("TEST PASSED: %s: key %s data " 262 "matches\n", test->ht_file, spd->hs_key); 263 } 264 break; 265 case DATA_TYPE_UINT8_ARRAY: 266 nvret = nvlist_lookup_uint8_array(nvl, spd->hs_key, 267 &u8a, &nents); 268 if (nvret != 0) { 269 warnc(nvret, "TEST FAILED: %s: failed to " 270 "lookup key %s", test->ht_file, 271 spd->hs_key); 272 ret = false; 273 break; 274 } 275 276 if (nents != spd->hs_val.hs_u8a.ha_nval) { 277 warnx("TEST FAILED: %s: key %s array has 0x%x " 278 "values, but expected 0x%x values", 279 test->ht_file, spd->hs_key, nents, 280 spd->hs_val.hs_u8a.ha_nval); 281 ret = false; 282 break; 283 } 284 285 pass = true; 286 for (uint_t i = 0; i < nents; i++) { 287 uint8_t targ = spd->hs_val.hs_u8a.ha_vals[i]; 288 if (u8a[i] != targ) { 289 warnx("TEST FAILED: %s: key %s: entry " 290 "[%u] has value 0x%x, but expected " 291 "0x%x", test->ht_file, spd->hs_key, 292 i, u8a[i], targ); 293 ret = false; 294 pass = false; 295 } 296 } 297 298 if (pass) { 299 (void) printf("TEST PASSED: %s: key %s data " 300 "matches\n", test->ht_file, spd->hs_key); 301 } 302 break; 303 case DATA_TYPE_UINT32_ARRAY: 304 nvret = nvlist_lookup_uint32_array(nvl, spd->hs_key, 305 &u32a, &nents); 306 if (nvret != 0) { 307 warnc(nvret, "TEST FAILED: %s: failed to " 308 "lookup key %s", test->ht_file, 309 spd->hs_key); 310 ret = false; 311 break; 312 } 313 314 if (nents != spd->hs_val.hs_u32a.ha_nval) { 315 warnx("TEST FAILED: %s: key %s array has 0x%x " 316 "values, but expected 0x%x values", 317 test->ht_file, spd->hs_key, nents, 318 spd->hs_val.hs_u32a.ha_nval); 319 ret = false; 320 break; 321 } 322 323 pass = true; 324 for (uint_t i = 0; i < nents; i++) { 325 uint32_t targ = spd->hs_val.hs_u32a.ha_vals[i]; 326 if (u32a[i] != targ) { 327 warnx("TEST FAILED: %s: key %s: entry " 328 "[%u] has value 0x%x, but expected " 329 "0x%x", test->ht_file, spd->hs_key, 330 i, u32a[i], targ); 331 ret = false; 332 pass = false; 333 } 334 } 335 336 if (pass) { 337 (void) printf("TEST PASSED: %s: key %s data " 338 "matches\n", test->ht_file, spd->hs_key); 339 } 340 break; 341 case DATA_TYPE_UINT64_ARRAY: 342 nvret = nvlist_lookup_uint64_array(nvl, spd->hs_key, 343 &u64a, &nents); 344 if (nvret != 0) { 345 warnc(nvret, "TEST FAILED: %s: failed to " 346 "lookup key %s", test->ht_file, 347 spd->hs_key); 348 ret = false; 349 break; 350 } 351 352 if (nents != spd->hs_val.hs_u64a.ha_nval) { 353 warnx("TEST FAILED: %s: key %s array has 0x%x " 354 "values, but expected 0x%x values", 355 test->ht_file, spd->hs_key, nents, 356 spd->hs_val.hs_u64a.ha_nval); 357 ret = false; 358 break; 359 } 360 361 pass = true; 362 for (uint_t i = 0; i < nents; i++) { 363 uint64_t targ = spd->hs_val.hs_u64a.ha_vals[i]; 364 if (u64a[i] != targ) { 365 warnx("TEST FAILED: %s: key %s: entry " 366 "[%u] has value 0x%" PRIx64 ", but " 367 "expected 0x%" PRIx64, 368 test->ht_file, spd->hs_key, i, 369 u64a[i], targ); 370 ret = false; 371 pass = false; 372 } 373 } 374 375 if (pass) { 376 (void) printf("TEST PASSED: %s: key %s data " 377 "matches\n", test->ht_file, spd->hs_key); 378 } 379 break; 380 381 case DATA_TYPE_BOOLEAN: 382 nvret = nvlist_lookup_boolean(nvl, spd->hs_key); 383 if (spd->hs_val.hs_bool) { 384 if (nvret != 0) { 385 warnc(nvret, "TEST FAILED: %s: failed " 386 "to lookup key %s", test->ht_file, 387 spd->hs_key); 388 ret = false; 389 } else { 390 (void) printf("TEST PASSED: %s: key %s " 391 "data matches\n", test->ht_file, 392 spd->hs_key); 393 } 394 } else { 395 if (nvret == 0) { 396 warnc(nvret, "TEST FAILED: %s: " 397 "successfully lookup up key %s, " 398 "but expected it not to be present", 399 test->ht_file, spd->hs_key); 400 ret = false; 401 } else if (nvret != ENOENT) { 402 warnx("TEST FAILED: %s: failed to " 403 "lookup key %s, but got %s not " 404 "ENOENT", test->ht_file, 405 spd->hs_key, 406 strerrorname_np(nvret)); 407 ret = false; 408 } else { 409 (void) printf("TEST PASSED: %s: key %s " 410 "data matches\n", test->ht_file, 411 spd->hs_key); 412 } 413 } 414 break; 415 case DATA_TYPE_BOOLEAN_ARRAY: 416 nvret = nvlist_lookup_boolean_array(nvl, spd->hs_key, 417 &ba, &nents); 418 if (nvret != 0) { 419 warnc(nvret, "TEST FAILED: %s: failed to " 420 "lookup key %s", test->ht_file, 421 spd->hs_key); 422 ret = false; 423 break; 424 } 425 426 if (nents != spd->hs_val.hs_ba.ha_nval) { 427 warnx("TEST FAILED: %s: key %s array has 0x%x " 428 "values, but expected 0x%x values", 429 test->ht_file, spd->hs_key, nents, 430 spd->hs_val.hs_u32a.ha_nval); 431 ret = false; 432 break; 433 } 434 435 pass = true; 436 for (uint_t i = 0; i < nents; i++) { 437 boolean_t targ = spd->hs_val.hs_ba.ha_vals[i]; 438 if (ba[i] != targ) { 439 warnx("TEST FAILED: %s: key %s: entry " 440 "[%u] is %s, but expected %s", 441 test->ht_file, spd->hs_key, i, 442 ba[i] ? "true" : "false", 443 targ ? "true" : "false"); 444 ret = false; 445 pass = false; 446 } 447 } 448 449 if (pass) { 450 (void) printf("TEST PASSED: %s: key %s data " 451 "matches\n", test->ht_file, spd->hs_key); 452 } 453 break; 454 default: 455 warnx("TEST FAILURE: %s: key %s has unsupported " 456 "data type 0x%x", test->ht_file, spd->hs_key, 457 spd->hs_type); 458 ret = false; 459 break; 460 } 461 } 462 463 nvlist_free(nvl); 464 return (ret); 465 } 466 467 int 468 main(void) 469 { 470 int ret = EXIT_SUCCESS; 471 const char *dir; 472 473 dir = getenv("HEX2SPD_DIR"); 474 if (dir == NULL) { 475 dir = SPD_DATA_DIR; 476 } 477 478 for (size_t i = 0; i < ARRAY_SIZE(hex2spd_tests); i++) { 479 if (!hex2spd_test_one(dir, hex2spd_tests[i])) 480 ret = EXIT_FAILURE; 481 } 482 483 if (ret == EXIT_SUCCESS) { 484 (void) printf("All tests passed successfully!\n"); 485 } 486 return (ret); 487 } 488