/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2024 Oxide Computer Company */ /* * This test goes through and converts data files that are in a semi-custom hex * format that represent DIMMs into binary data and then tries to parse the SPD * data. It then looks at specific fields and flags from within them. Each * module is expected to be parsed error free. * * Tests are organized in files around the DDR module type generation, e.g. * DDR3, DDR4, and DDR5 are all found in different directories. SPD information * that we use has been taken from a combination of dumping data from actual * modules and transforming them, transforming tables that are distributed by * vendors in datasheets and supplemental, and manually creating SPD * information based on information in datasheets. In particular, LPDDR in its * solder package does not actually include SPD information directly and * therefore we have translated it. This is what Intel and AMD have recommended * and do for their LPDDR bootstrapping. */ #include #include #include #include #include #include #include #include #include #include #include #include "libjedec_hex2spd.h" /* * Default directory for data. */ #define SPD_DATA_DIR "/opt/util-tests/tests/hex2spd" /* * Maximum size we'll tolerate for a file. This corresponds to the largest DDR5 * SPD. */ #define SPD_MAX 2048 static const hex2spd_test_t *hex2spd_tests[] = { µn_ddr4_rdimm, &samsung_ddr4_lrdimm, &advantech_ddr4_sodimm, &advantech_ddr4_udimm, µn_ddr5_rdimm, &advantech_ddr5_rdimm, µn_lp4, &nanya_lp3, µn_lp5, &fake_lp5_camm2, &samsung_ddr3_rdimm, µn_ddr3_lrdimm }; /* * Logic to convert an ASCII file with Hex to SPD data. Each byte of data is * expected to be 2 hex digits conventionally arranged 16 bytes across. At any * point we encounter a '#' character, we treat that as a comment and ignore the * rest of the line. A line will start with an address followed by a ':'. */ static void * hex2spd(const char *path, uint32_t *lenp) { char *buf = NULL; size_t buflen = 0; uint8_t *out = malloc(SPD_MAX); uint32_t outlen = 0, curline = 0; FILE *f; f = fopen(path, "r"); if (f == NULL) { warnx("INTERNAL TEST ERROR: failed to find test file %s", path); free(out); return (NULL); } if (out == NULL) { err(EXIT_FAILURE, "failed to allocate %u bytes for buffer", SPD_MAX); } while (getline(&buf, &buflen, f) != -1) { char *comment, *colon; unsigned long dataoff; curline++; if ((comment = strchr(buf, '#')) != NULL) { *comment = '\0'; } if (*buf == '\0') continue; /* * First grab out a line offset marker. This should be in order, * but future us may be end up wanting to skip lots of zeros of * course. */ errno = 0; dataoff = strtoul(buf, &colon, 16); if (errno != 0 || *colon != ':' || *(colon + 1) != ' ') { errx(EXIT_FAILURE, "failed to parse address part of " "line %u", curline); } if (dataoff >= SPD_MAX || dataoff % 0x10 != 0) { errx(EXIT_FAILURE, "line %u parsed data offset %lu is " "invalid", curline, dataoff); } /* * We've got the starting data offset. Now go ahead and parse * all the actual data that's in here. We use the max power way. */ if (sscanf(colon + 2, "%02x %02x %02x %02x %02x %02x %02x %02x " "%02x %02x %02x %02x %02x %02x %02x %02x", &out[dataoff + 0], &out[dataoff + 1], &out[dataoff + 2], &out[dataoff + 3], &out[dataoff + 4], &out[dataoff + 5], &out[dataoff + 6], &out[dataoff + 7], &out[dataoff + 8], &out[dataoff + 9], &out[dataoff + 10], &out[dataoff + 11], &out[dataoff + 12], &out[dataoff + 13], &out[dataoff + 14], &out[dataoff + 15]) != 16) { errx(EXIT_FAILURE, "failed to parse data from line %u", curline); } outlen = MAX(outlen, dataoff + 16); } *lenp = outlen; VERIFY0(fclose(f)); return (out); } static bool hex2spd_test_one(const char *dir, const hex2spd_test_t *test) { char path[PATH_MAX]; void *data; uint32_t dlen; nvlist_t *nvl; spd_error_t spd_err; bool ret = true; if (snprintf(path, sizeof (path), "%s/%s.spd", dir, test->ht_file) >= sizeof (path)) { errx(EXIT_FAILURE, "INTERNAL TEST ERROR: constructing test " "path for %s would have overflowed internal buffer", test->ht_file); } data = hex2spd(path, &dlen); if (data == NULL) { return (false); } nvl = libjedec_spd(data, dlen, &spd_err); free(data); if (spd_err != LIBJEDEC_SPD_OK) { warnx("TEST FAILURE: failed to parse %s: 0x%x", path, spd_err); return (false); } (void) printf("TEST PASSED: initially parsed %s\n", test->ht_file); /* * Verify there are no errors in this data. This means that we shouldn't * find the errors key or the incomplete key. */ if (nvlist_exists(nvl, SPD_KEY_ERRS)) { warnx("TEST FAILED: %s contains errors:", test->ht_file); dump_nvlist(nvl, 0); ret = false; } if (nvlist_exists(nvl, SPD_KEY_INCOMPLETE)) { ret = false; warnx("TEST FAILED: %s flagged as incomplete:", test->ht_file); dump_nvlist(nvl, 0); } for (const hex2spd_spd_t *spd = &test->ht_checks[0]; spd->hs_key != NULL; spd++) { int nvret; uint_t nents; uint8_t *u8a; uint32_t u32, *u32a; uint64_t u64, *u64a; boolean_t *ba; char *str; bool pass; switch (spd->hs_type) { case DATA_TYPE_UINT32: nvret = nvlist_lookup_uint32(nvl, spd->hs_key, &u32); if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed to " "lookup key %s", test->ht_file, spd->hs_key); ret = false; } else if (u32 != spd->hs_val.hs_u32) { warnx("TEST FAILED: %s: key %s: found value " "0x%x, but expected 0x%x", test->ht_file, spd->hs_key, u32, spd->hs_val.hs_u32); ret = false; } else { (void) printf("TEST PASSED: %s: key %s data " "matches\n", test->ht_file, spd->hs_key); } break; case DATA_TYPE_UINT64: nvret = nvlist_lookup_uint64(nvl, spd->hs_key, &u64); if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed to " "lookup key %s", test->ht_file, spd->hs_key); ret = false; } else if (u64 != spd->hs_val.hs_u64) { warnx("TEST FAILED: %s: key %s: found value " "0x%" PRIx64 ", but expected 0x%" PRIx64, test->ht_file, spd->hs_key, u64, spd->hs_val.hs_u64); ret = false; } else { (void) printf("TEST PASSED: %s: key %s data " "matches\n", test->ht_file, spd->hs_key); } break; case DATA_TYPE_STRING: nvret = nvlist_lookup_string(nvl, spd->hs_key, &str); if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed to " "lookup key %s", test->ht_file, spd->hs_key); ret = false; } else if (strcmp(str, spd->hs_val.hs_str) != 0) { warnx("TEST FAILED: %s: key %s: found value " "%s, but expected %s", test->ht_file, spd->hs_key, str, spd->hs_val.hs_str); ret = false; } else { (void) printf("TEST PASSED: %s: key %s data " "matches\n", test->ht_file, spd->hs_key); } break; case DATA_TYPE_UINT8_ARRAY: nvret = nvlist_lookup_uint8_array(nvl, spd->hs_key, &u8a, &nents); if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed to " "lookup key %s", test->ht_file, spd->hs_key); ret = false; break; } if (nents != spd->hs_val.hs_u8a.ha_nval) { warnx("TEST FAILED: %s: key %s array has 0x%x " "values, but expected 0x%x values", test->ht_file, spd->hs_key, nents, spd->hs_val.hs_u8a.ha_nval); ret = false; break; } pass = true; for (uint_t i = 0; i < nents; i++) { uint8_t targ = spd->hs_val.hs_u8a.ha_vals[i]; if (u8a[i] != targ) { warnx("TEST FAILED: %s: key %s: entry " "[%u] has value 0x%x, but expected " "0x%x", test->ht_file, spd->hs_key, i, u8a[i], targ); ret = false; pass = false; } } if (pass) { (void) printf("TEST PASSED: %s: key %s data " "matches\n", test->ht_file, spd->hs_key); } break; case DATA_TYPE_UINT32_ARRAY: nvret = nvlist_lookup_uint32_array(nvl, spd->hs_key, &u32a, &nents); if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed to " "lookup key %s", test->ht_file, spd->hs_key); ret = false; break; } if (nents != spd->hs_val.hs_u32a.ha_nval) { warnx("TEST FAILED: %s: key %s array has 0x%x " "values, but expected 0x%x values", test->ht_file, spd->hs_key, nents, spd->hs_val.hs_u32a.ha_nval); ret = false; break; } pass = true; for (uint_t i = 0; i < nents; i++) { uint32_t targ = spd->hs_val.hs_u32a.ha_vals[i]; if (u32a[i] != targ) { warnx("TEST FAILED: %s: key %s: entry " "[%u] has value 0x%x, but expected " "0x%x", test->ht_file, spd->hs_key, i, u32a[i], targ); ret = false; pass = false; } } if (pass) { (void) printf("TEST PASSED: %s: key %s data " "matches\n", test->ht_file, spd->hs_key); } break; case DATA_TYPE_UINT64_ARRAY: nvret = nvlist_lookup_uint64_array(nvl, spd->hs_key, &u64a, &nents); if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed to " "lookup key %s", test->ht_file, spd->hs_key); ret = false; break; } if (nents != spd->hs_val.hs_u64a.ha_nval) { warnx("TEST FAILED: %s: key %s array has 0x%x " "values, but expected 0x%x values", test->ht_file, spd->hs_key, nents, spd->hs_val.hs_u64a.ha_nval); ret = false; break; } pass = true; for (uint_t i = 0; i < nents; i++) { uint64_t targ = spd->hs_val.hs_u64a.ha_vals[i]; if (u64a[i] != targ) { warnx("TEST FAILED: %s: key %s: entry " "[%u] has value 0x%" PRIx64 ", but " "expected 0x%" PRIx64, test->ht_file, spd->hs_key, i, u64a[i], targ); ret = false; pass = false; } } if (pass) { (void) printf("TEST PASSED: %s: key %s data " "matches\n", test->ht_file, spd->hs_key); } break; case DATA_TYPE_BOOLEAN: nvret = nvlist_lookup_boolean(nvl, spd->hs_key); if (spd->hs_val.hs_bool) { if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed " "to lookup key %s", test->ht_file, spd->hs_key); ret = false; } else { (void) printf("TEST PASSED: %s: key %s " "data matches\n", test->ht_file, spd->hs_key); } } else { if (nvret == 0) { warnc(nvret, "TEST FAILED: %s: " "successfully lookup up key %s, " "but expected it not to be present", test->ht_file, spd->hs_key); ret = false; } else if (nvret != ENOENT) { warnx("TEST FAILED: %s: failed to " "lookup key %s, but got %s not " "ENOENT", test->ht_file, spd->hs_key, strerrorname_np(nvret)); ret = false; } else { (void) printf("TEST PASSED: %s: key %s " "data matches\n", test->ht_file, spd->hs_key); } } break; case DATA_TYPE_BOOLEAN_ARRAY: nvret = nvlist_lookup_boolean_array(nvl, spd->hs_key, &ba, &nents); if (nvret != 0) { warnc(nvret, "TEST FAILED: %s: failed to " "lookup key %s", test->ht_file, spd->hs_key); ret = false; break; } if (nents != spd->hs_val.hs_ba.ha_nval) { warnx("TEST FAILED: %s: key %s array has 0x%x " "values, but expected 0x%x values", test->ht_file, spd->hs_key, nents, spd->hs_val.hs_u32a.ha_nval); ret = false; break; } pass = true; for (uint_t i = 0; i < nents; i++) { boolean_t targ = spd->hs_val.hs_ba.ha_vals[i]; if (ba[i] != targ) { warnx("TEST FAILED: %s: key %s: entry " "[%u] is %s, but expected %s", test->ht_file, spd->hs_key, i, ba[i] ? "true" : "false", targ ? "true" : "false"); ret = false; pass = false; } } if (pass) { (void) printf("TEST PASSED: %s: key %s data " "matches\n", test->ht_file, spd->hs_key); } break; default: warnx("TEST FAILURE: %s: key %s has unsupported " "data type 0x%x", test->ht_file, spd->hs_key, spd->hs_type); ret = false; break; } } nvlist_free(nvl); return (ret); } int main(void) { int ret = EXIT_SUCCESS; const char *dir; dir = getenv("HEX2SPD_DIR"); if (dir == NULL) { dir = SPD_DATA_DIR; } for (size_t i = 0; i < ARRAY_SIZE(hex2spd_tests); i++) { if (!hex2spd_test_one(dir, hex2spd_tests[i])) ret = EXIT_FAILURE; } if (ret == EXIT_SUCCESS) { (void) printf("All tests passed successfully!\n"); } return (ret); }