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 *
hex2spd(const char * path,uint32_t * lenp)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
hex2spd_test_one(const char * dir,const hex2spd_test_t * test)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
main(void)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