xref: /illumos-gate/usr/src/test/util-tests/tests/libjedec/hex2spd/libjedec_hex2spd.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
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 	&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 };
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