xref: /illumos-gate/usr/src/lib/libjedec/common/libjedec_spd.c (revision e00bdde3c6d406f40f53f3025defadc22f7ec31a)
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 2023 Oxide Computer Company
14  */
15 
16 /*
17  * This is the common file or parsing out SPD data of different generations. Our
18  * general goal is to create a single nvlist_t that has a few different sections
19  * present in it:
20  *
21  *   o Metadata (e.g. DRAM type, Revision, overlay type, etc.)
22  *   o Manufacturing Information
23  *   o Common parameters: these are ultimately specific to a DDR type.
24  *   o Overlay parameters: these are specific to both the DDR type and the
25  *     module type.
26  *
27  * We try to only fail top-level parsing if we really can't understand anything
28  * or don't have enough information. We assume that we'll get relatively
29  * complete data. Errors are listed as keys for a given entry and will be
30  * skipped otherwise. For an overview of the actual fields and structures, see
31  * libjedec.h.
32  *
33  * Currently we support all of DDR4 and DDD5 based SPD information with the
34  * exception of some NVDIMM properties.
35  */
36 
37 #include <string.h>
38 #include <sys/debug.h>
39 #include <sys/sysmacros.h>
40 #include <ctype.h>
41 #include <stdarg.h>
42 #include <errno.h>
43 #include <stdbool.h>
44 
45 #include "libjedec_spd.h"
46 
47 void
48 spd_nvl_err(spd_info_t *si, const char *key, spd_error_kind_t err,
49     const char *fmt, ...)
50 {
51 	int ret;
52 	nvlist_t *nvl;
53 	char msg[1024];
54 	va_list ap;
55 
56 	if (si->si_error != LIBJEDEC_SPD_OK)
57 		return;
58 
59 	ret = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0);
60 	if (ret != 0) {
61 		VERIFY3S(ret, ==, ENOMEM);
62 		si->si_error = LIBJEDEC_SPD_NOMEM;
63 		return;
64 	}
65 
66 	ret = nvlist_add_uint32(nvl, SPD_KEY_ERRS_CODE, err);
67 	if (ret != 0) {
68 		VERIFY3S(ret, ==, ENOMEM);
69 		nvlist_free(nvl);
70 		si->si_error = LIBJEDEC_SPD_NOMEM;
71 		return;
72 	}
73 
74 	/*
75 	 * We cast this snprintf to void so we can try to get someone something
76 	 * at least in the face of it somehow being too large.
77 	 */
78 	va_start(ap, fmt);
79 	(void) vsnprintf(msg, sizeof (msg), fmt, ap);
80 	va_end(ap);
81 
82 	ret = nvlist_add_string(nvl, SPD_KEY_ERRS_MSG, msg);
83 	if (ret != 0) {
84 		VERIFY3S(ret, ==, ENOMEM);
85 		nvlist_free(nvl);
86 		si->si_error = LIBJEDEC_SPD_NOMEM;
87 		return;
88 	}
89 
90 	ret = nvlist_add_nvlist(si->si_errs, key, nvl);
91 	if (ret != 0) {
92 		VERIFY3S(ret, ==, ENOMEM);
93 		nvlist_free(nvl);
94 		si->si_error = LIBJEDEC_SPD_NOMEM;
95 		return;
96 	}
97 
98 	nvlist_free(nvl);
99 }
100 
101 void
102 spd_nvl_insert_str(spd_info_t *si, const char *key, const char *data)
103 {
104 	int ret;
105 
106 	if (si->si_error != LIBJEDEC_SPD_OK)
107 		return;
108 
109 	ret = nvlist_add_string(si->si_nvl, key, data);
110 	if (ret != 0) {
111 		VERIFY3S(ret, ==, ENOMEM);
112 		si->si_error = LIBJEDEC_SPD_NOMEM;
113 		return;
114 	}
115 }
116 
117 void
118 spd_nvl_insert_u32(spd_info_t *si, const char *key, uint32_t data)
119 {
120 	int ret;
121 
122 	if (si->si_error != LIBJEDEC_SPD_OK)
123 		return;
124 
125 	ret = nvlist_add_uint32(si->si_nvl, key, data);
126 	if (ret != 0) {
127 		VERIFY3S(ret, ==, ENOMEM);
128 		si->si_error = LIBJEDEC_SPD_NOMEM;
129 		return;
130 	}
131 }
132 
133 void
134 spd_nvl_insert_u64(spd_info_t *si, const char *key, uint64_t data)
135 {
136 	int ret;
137 
138 	if (si->si_error != LIBJEDEC_SPD_OK)
139 		return;
140 
141 	ret = nvlist_add_uint64(si->si_nvl, key, data);
142 	if (ret != 0) {
143 		VERIFY3S(ret, ==, ENOMEM);
144 		si->si_error = LIBJEDEC_SPD_NOMEM;
145 		return;
146 	}
147 }
148 
149 void
150 spd_nvl_insert_u32_array(spd_info_t *si, const char *key,
151     uint32_t *data, uint_t nent)
152 {
153 	int ret;
154 
155 	if (si->si_error != LIBJEDEC_SPD_OK)
156 		return;
157 
158 	ret = nvlist_add_uint32_array(si->si_nvl, key, data, nent);
159 	if (ret != 0) {
160 		VERIFY3S(ret, ==, ENOMEM);
161 		si->si_error = LIBJEDEC_SPD_NOMEM;
162 		return;
163 	}
164 }
165 
166 void
167 spd_nvl_insert_key(spd_info_t *si, const char *key)
168 {
169 	int ret;
170 
171 	if (si->si_error != LIBJEDEC_SPD_OK)
172 		return;
173 
174 	ret = nvlist_add_boolean(si->si_nvl, key);
175 	if (ret != 0) {
176 		VERIFY3S(ret, ==, ENOMEM);
177 		si->si_error = LIBJEDEC_SPD_NOMEM;
178 		return;
179 	}
180 }
181 
182 void
183 spd_insert_map(spd_info_t *si, const char *key, uint8_t spd_val,
184     const spd_value_map_t *maps, size_t nmaps)
185 {
186 	for (size_t i = 0; i < nmaps; i++) {
187 		if (maps[i].svm_spd != spd_val)
188 			continue;
189 		if (maps[i].svm_skip)
190 			return;
191 
192 		spd_nvl_insert_u32(si, key, maps[i].svm_use);
193 		return;
194 	}
195 
196 	spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "encountered unknown "
197 	    "value: 0x%x", spd_val);
198 }
199 
200 void
201 spd_insert_map64(spd_info_t *si, const char *key, uint8_t spd_val,
202     const spd_value_map64_t *maps, size_t nmaps)
203 {
204 	for (size_t i = 0; i < nmaps; i++) {
205 		if (maps[i].svm_spd != spd_val)
206 			continue;
207 		if (maps[i].svm_skip)
208 			return;
209 
210 		spd_nvl_insert_u64(si, key, maps[i].svm_use);
211 		return;
212 	}
213 
214 	spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "encountered unknown "
215 	    "value: 0x%x", spd_val);
216 }
217 
218 void
219 spd_insert_str_map(spd_info_t *si, const char *key, uint8_t spd_val,
220     const spd_str_map_t *maps, size_t nmaps)
221 {
222 	for (size_t i = 0; i < nmaps; i++) {
223 		if (maps[i].ssm_spd != spd_val)
224 			continue;
225 		if (maps[i].ssm_skip)
226 			return;
227 
228 		spd_nvl_insert_str(si, key, maps[i].ssm_str);
229 		return;
230 	}
231 
232 	spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "encountered unknown "
233 	    "value: 0x%x", spd_val);
234 }
235 
236 /*
237  * Map an array in its entirety to a corresponding set of values. If any one
238  * value cannot be translated, then we fail the whole item.
239  */
240 void
241 spd_insert_map_array(spd_info_t *si, const char *key, const uint8_t *raw,
242     size_t nraw, const spd_value_map_t *maps, size_t nmaps)
243 {
244 	uint32_t *trans;
245 
246 	trans = calloc(nraw, sizeof (uint32_t));
247 	if (trans == NULL) {
248 		si->si_error = LIBJEDEC_SPD_NOMEM;
249 		return;
250 	}
251 
252 	for (size_t i = 0; i < nraw; i++) {
253 		bool found = false;
254 		for (size_t map = 0; map < nmaps; map++) {
255 			if (maps[map].svm_spd != raw[i])
256 				continue;
257 			ASSERT3U(maps[map].svm_skip, ==, false);
258 			found = true;
259 			trans[i] = maps[map].svm_use;
260 			break;
261 		}
262 
263 		if (!found) {
264 			spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "encountered "
265 			    "unknown array value: [%zu]=0x%x", i, raw[i]);
266 			goto done;
267 		}
268 	}
269 
270 	spd_nvl_insert_u32_array(si, key, trans, nraw);
271 done:
272 	free(trans);
273 }
274 
275 void
276 spd_insert_range(spd_info_t *si, const char *key, uint8_t raw_val,
277     const spd_value_range_t *range)
278 {
279 	/*
280 	 * Apply any base or multiple to the value. If the min or max are zero,
281 	 * then we ignore them. We apply the base before a multiple.
282 	 */
283 	uint32_t min = 0, max = UINT32_MAX;
284 	uint32_t act = raw_val + range->svr_base;
285 
286 	if (range->svr_mult != 0) {
287 		act *= range->svr_mult;
288 	}
289 
290 	if (range->svr_max != 0) {
291 		max = range->svr_max;
292 	}
293 
294 	if (range->svr_min != 0) {
295 		min = range->svr_min;
296 	} else if (range->svr_base != 0) {
297 		min = range->svr_base;
298 	}
299 
300 	if (act > max || act < min) {
301 		spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "found value "
302 		    "0x%x (raw 0x%x) outside range [0x%x, 0x%x]", act, raw_val,
303 		    min, max);
304 	} else {
305 		spd_nvl_insert_u32(si, key, act);
306 	}
307 }
308 
309 /*
310  * Either insert the given flag for a key or OR it in if it already exists.
311  */
312 void
313 spd_upsert_flag(spd_info_t *si, const char *key, uint32_t flag)
314 {
315 	int ret;
316 	uint32_t val;
317 
318 	ret = nvlist_lookup_uint32(si->si_nvl, key, &val);
319 	if (ret != 0) {
320 		VERIFY3S(ret, ==, ENOENT);
321 		spd_nvl_insert_u32(si, key, flag);
322 		return;
323 	}
324 
325 	VERIFY0(val & flag);
326 	val |= flag;
327 	spd_nvl_insert_u32(si, key, val);
328 }
329 
330 void
331 spd_parse_rev(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
332 {
333 	const uint8_t data = si->si_data[off];
334 	const uint8_t enc = SPD_DDR4_SPD_REV_ENC(data);
335 	const uint8_t add = SPD_DDR4_SPD_REV_ENC(data);
336 
337 	spd_nvl_insert_u32(si, SPD_KEY_REV_ENC, enc);
338 	spd_nvl_insert_u32(si, SPD_KEY_REV_ADD, add);
339 }
340 
341 void
342 spd_parse_jedec_id(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
343 {
344 	uint32_t id[2];
345 
346 	VERIFY3U(len, ==, 2);
347 	id[0] = SPD_MFG_ID0_CONT(si->si_data[off]);
348 	id[1] = si->si_data[off + 1];
349 
350 	spd_nvl_insert_u32_array(si, key, id, ARRAY_SIZE(id));
351 }
352 
353 void
354 spd_parse_jedec_id_str(spd_info_t *si, uint32_t off, uint32_t len,
355     const char *key)
356 {
357 	uint8_t cont = SPD_MFG_ID0_CONT(si->si_data[off]);
358 	const char *str;
359 
360 	VERIFY3U(len, ==, 2);
361 	str = libjedec_vendor_string(cont, si->si_data[off + 1]);
362 	if (str != NULL) {
363 		spd_nvl_insert_str(si, key, str);
364 	} else {
365 		spd_nvl_err(si, key, SPD_ERROR_NO_XLATE, "no matching "
366 		    "libjedec vendor string for 0x%x,0x%x", cont,
367 		    si->si_data[off + 1]);
368 	}
369 }
370 
371 /*
372  * Parse a string that is at most len bytes wide and is padded with spaces. If
373  * the string contains an unprintable, then we will not pull this off and set an
374  * error for the string's key. 128 bytes should be larger than any ascii string
375  * that we encounter as that is the size of most regions in SPD data.
376  */
377 void
378 spd_parse_string(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
379 {
380 	uint32_t nbytes = 0;
381 	char buf[128];
382 
383 	VERIFY3U(sizeof (buf), >, len);
384 	for (uint32_t i = 0; i < len; i++) {
385 		if (si->si_data[off + i] == ' ') {
386 			nbytes = i;
387 			break;
388 		}
389 
390 		if (isascii(si->si_data[off + i]) == 0 ||
391 		    isprint(si->si_data[off + i]) == 0) {
392 			spd_nvl_err(si, key, SPD_ERROR_UNPRINT,
393 			    "byte %u for key %s (off: 0x%x, val: 0x%x) is not "
394 			    "printable", i, key, off + 1,
395 			    si->si_data[off + i]);
396 			return;
397 		}
398 	}
399 
400 	if (nbytes == 0) {
401 		spd_nvl_err(si, key, SPD_ERROR_NO_DATA, "key %s has "
402 		    "no valid bytes in the string", key);
403 		return;
404 	}
405 
406 	(void) memcpy(buf, &si->si_data[off], nbytes);
407 	buf[nbytes] = '\0';
408 	spd_nvl_insert_str(si, key, buf);
409 }
410 
411 /*
412  * Turn an array of bytes into a hex string. We need to allocate up to two bytes
413  * per length that we have. We always zero pad such strings. We statically size
414  * our buffer because the largest such string we have right now is a 4-byte
415  * serial number. With the 128 byte buffer below, we could deal with a length up
416  * to 63 (far beyond what we expect to ever see).
417  */
418 void
419 spd_parse_hex_string(spd_info_t *si, uint32_t off, uint32_t len,
420     const char *key)
421 {
422 	char buf[128];
423 	size_t nwrite = 0;
424 
425 	VERIFY3U(sizeof (buf), >=, len * 2 + 1);
426 
427 	for (uint32_t i = 0; i < len; i++) {
428 		int ret = snprintf(buf + nwrite, sizeof (buf) - nwrite,
429 		    "%02X", si->si_data[off + i]);
430 		if (ret < 0) {
431 			spd_nvl_err(si, key, SPD_ERROR_INTERNAL,
432 			    "snprintf failed unexpectedly for key %s: %s",
433 			    key, strerror(errno));
434 			return;
435 		}
436 
437 		VERIFY3U(ret, ==, 2);
438 		nwrite += ret;
439 	}
440 
441 	spd_nvl_insert_str(si, key, buf);
442 }
443 
444 /*
445  * Several SPD keys are explicit BCD major and minor versions in a given nibble.
446  * This is most common in DDR5, but otherwise one should probably use
447  * spd_parse_hex_string().
448  */
449 void
450 spd_parse_hex_vers(spd_info_t *si, uint32_t off, uint32_t len,
451     const char *key)
452 {
453 	const uint8_t data = si->si_data[off];
454 	const uint8_t maj = bitx8(data, 7, 4);
455 	const uint8_t min = bitx8(data, 3, 0);
456 	char buf[128];
457 
458 	VERIFY3U(len, ==, 1);
459 
460 	int ret = snprintf(buf, sizeof (buf), "%x.%x", maj, min);
461 	if (ret < 0) {
462 		spd_nvl_err(si, key, SPD_ERROR_INTERNAL,
463 		    "snprintf failed unexpectedly for key %s: %s",
464 		    key, strerror(errno));
465 		return;
466 	}
467 
468 	spd_nvl_insert_str(si, key, buf);
469 }
470 
471 void
472 spd_parse_raw_u8(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
473 {
474 	VERIFY3U(len, ==, 1);
475 	spd_nvl_insert_u32(si, key, si->si_data[off]);
476 }
477 
478 void
479 spd_parse_dram_step(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
480 {
481 	VERIFY3U(len, ==, 1);
482 
483 	if (si->si_data[off] == SPD_DRAM_STEP_NOINFO)
484 		return;
485 
486 	spd_parse_hex_string(si, off, len, key);
487 }
488 
489 /*
490  * Height and thickness have the same meaning across DDR3-DDR5.
491  */
492 static const spd_value_range_t spd_height_range = {
493 	.svr_base = SPD_DDR5_COM_HEIGHT_BASE
494 };
495 
496 static const spd_value_range_t spd_thick_range = {
497 	.svr_base = SPD_DDR5_COM_THICK_BASE
498 };
499 
500 void
501 spd_parse_height(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
502 {
503 	const uint8_t data = si->si_data[off];
504 	const uint8_t height = SPD_DDR5_COM_HEIGHT_MM(data);
505 	spd_insert_range(si, key, height, &spd_height_range);
506 }
507 
508 void
509 spd_parse_thickness(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
510 {
511 	const uint8_t data = si->si_data[off];
512 	const uint8_t front = SPD_DDR5_COM_THICK_FRONT(data);
513 	const uint8_t back = SPD_DDR5_COM_THICK_FRONT(data);
514 
515 	spd_insert_range(si, SPD_KEY_MOD_FRONT_THICK, front, &spd_thick_range);
516 	spd_insert_range(si, SPD_KEY_MOD_BACK_THICK, back, &spd_thick_range);
517 }
518 
519 /*
520  * Calculate the DRAM CRC16. The crc ranges over [ off, off + len - 2). The crc
521  * lsb is at off + len - 2, and the msb is at off + len - 1. The JEDEC specs
522  * describe the algorithm (e.g. 21-C Annex L, 8.1.53).
523  */
524 void
525 spd_parse_crc(spd_info_t *si, uint32_t off, uint32_t len, const char *key)
526 {
527 	uint32_t crc = 0;
528 	const uint16_t expect = si->si_data[off + len - 2] |
529 	    (si->si_data[off + len - 1] << 8);
530 
531 	for (uint32_t i = 0; i < len - 2; i++) {
532 		crc = crc ^ (uint32_t)si->si_data[off + i] << 8;
533 		for (uint32_t c = 0; c < 8; c++) {
534 			if (crc & 0x8000) {
535 				crc = crc << 1 ^ 0x1021;
536 			} else {
537 				crc = crc << 1;
538 			}
539 		}
540 	}
541 
542 	crc &= 0xffff;
543 	if (crc == expect) {
544 		spd_nvl_insert_u32(si, key, crc);
545 	} else {
546 		spd_nvl_err(si, key, SPD_ERROR_BAD_DATA, "crc mismatch: "
547 		    "expected 0x%x, found 0x%x", expect, crc);
548 	}
549 }
550 
551 void
552 spd_parse(spd_info_t *sip, const spd_parse_t *parse, size_t nparse)
553 {
554 	for (size_t i = 0; i < nparse; i++) {
555 		uint32_t len;
556 
557 		if (parse[i].sp_len != 0) {
558 			len = parse[i].sp_len;
559 		} else {
560 			len = 1;
561 		}
562 
563 		if (len + parse[i].sp_off >= sip->si_nbytes) {
564 			if ((sip->si_flags & SPD_INFO_F_INCOMPLETE) != 0)
565 				continue;
566 			sip->si_flags |= SPD_INFO_F_INCOMPLETE;
567 			ASSERT3U(parse[i].sp_off, <, UINT32_MAX);
568 			spd_nvl_insert_u32(sip, SPD_KEY_INCOMPLETE,
569 			    (uint32_t)parse[i].sp_off);
570 		} else {
571 			parse[i].sp_parse(sip, parse[i].sp_off, len,
572 			    parse[i].sp_key);
573 		}
574 
575 		if (sip->si_error != LIBJEDEC_SPD_OK) {
576 			return;
577 		}
578 	}
579 }
580 
581 static spd_error_t
582 spd_init_info(spd_info_t *sip)
583 {
584 	int ret;
585 
586 	if ((ret = nvlist_alloc(&sip->si_nvl, NV_UNIQUE_NAME, 0)) != 0) {
587 		VERIFY3S(ret, ==, ENOMEM);
588 		return (LIBJEDEC_SPD_NOMEM);
589 	}
590 
591 	if ((ret = nvlist_alloc(&sip->si_errs, NV_UNIQUE_NAME, 0)) != 0) {
592 		VERIFY3S(ret, ==, ENOMEM);
593 		return (LIBJEDEC_SPD_NOMEM);
594 	}
595 
596 	return (LIBJEDEC_SPD_OK);
597 }
598 
599 static void
600 spd_fini_info(spd_info_t *sip)
601 {
602 	nvlist_free(sip->si_nvl);
603 	nvlist_free(sip->si_errs);
604 }
605 
606 nvlist_t *
607 libjedec_spd(const uint8_t *buf, size_t nbytes, spd_error_t *err)
608 {
609 	int ret;
610 	spd_error_t set;
611 	spd_info_t si;
612 
613 	if (err == NULL) {
614 		err = &set;
615 	}
616 
617 	(void) memset(&si, 0, sizeof (spd_info_t));
618 	si.si_data = buf;
619 	si.si_nbytes = nbytes;
620 
621 	*err = spd_init_info(&si);
622 	if (si.si_error != LIBJEDEC_SPD_OK) {
623 		goto fatal;
624 	}
625 
626 	/*
627 	 * To begin parsing the SPD, we must first look at byte 2, which appears
628 	 * to almost always be the Key Byte / Host Bus Command Protocol Type
629 	 * which then tells us how the rest of the data is formatted.
630 	 */
631 	if (si.si_nbytes <= SPD_DRAM_TYPE) {
632 		*err = LIBJEDEC_SPD_TOOSHORT;
633 		goto fatal;
634 	}
635 
636 	si.si_error = LIBJEDEC_SPD_OK;
637 	si.si_dram = buf[SPD_DRAM_TYPE];
638 	switch (si.si_dram) {
639 	case SPD_DT_DDR4_SDRAM:
640 		spd_parse_ddr4(&si);
641 		break;
642 	case SPD_DT_DDR5_SDRAM:
643 		spd_parse_ddr5(&si);
644 		break;
645 	default:
646 		*err = LIBJEDEC_SPD_UNSUP_TYPE;
647 		goto fatal;
648 	}
649 
650 	/*
651 	 * We got everything, at this point add the error nvlist here.
652 	 */
653 	if (si.si_error == LIBJEDEC_SPD_OK) {
654 		if (!nvlist_empty(si.si_errs) &&
655 		    (ret = nvlist_add_nvlist(si.si_nvl, "errors",
656 		    si.si_errs)) != 0) {
657 			VERIFY3S(ret, ==, ENOMEM);
658 			*err = LIBJEDEC_SPD_NOMEM;
659 			goto fatal;
660 		}
661 		nvlist_free(si.si_errs);
662 		return (si.si_nvl);
663 	}
664 
665 	*err = si.si_error;
666 fatal:
667 	spd_fini_info(&si);
668 	return (NULL);
669 }
670