#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <strings.h>
#include <errno.h>
#include <sys/sysmacros.h>
#include <libnvpair.h>
#include <libjedec.h>
#include <stdbool.h>
#include <limits.h>
#include <sys/debug.h>
#include "libjedec_hex2spd.h"
#define SPD_DATA_DIR "/opt/util-tests/tests/hex2spd"
#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
};
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;
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);
}
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);
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);
}