xref: /illumos-gate/usr/src/test/util-tests/tests/libjedec/hex2spd/libjedec_hex2spd.c (revision ac2250cb76bb32944fd2c8a3ba2cd3f79747748d)
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 	&micron_ddr4_rdimm,
60 	&samsung_ddr4_lrdimm,
61 	&advantech_ddr4_sodimm,
62 	&advantech_ddr4_udimm,
63 	&micron_ddr5_rdimm,
64 	&advantech_ddr5_rdimm,
65 	&micron_lp4,
66 	&nanya_lp3,
67 	&micron_lp5,
68 	&fake_lp5_camm2,
69 	&samsung_ddr3_rdimm,
70 	&micron_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