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 constructs various failure cases for our SPD parsing logic and ensures
18  * that we can catch them.
19  */
20 
21 #include <err.h>
22 #include <stdlib.h>
23 #include <sys/sysmacros.h>
24 #include <libjedec.h>
25 
26 typedef struct {
27 	uint8_t lst_data[1024];
28 	size_t lst_len;
29 	spd_error_t lst_err;
30 	const char *lst_desc;
31 	boolean_t (*lst_check)(nvlist_t *);
32 } libjedec_spd_test_t;
33 
34 /*
35  * The test in question only specifies 0x10 bytes. This means we should have a
36  * valid errors nvl with an incomplete entry.
37  */
38 static boolean_t
spd_check_short_ddr4(nvlist_t * nvl)39 spd_check_short_ddr4(nvlist_t *nvl)
40 {
41 	int ret;
42 	uint32_t inc;
43 
44 	if ((ret = nvlist_lookup_uint32(nvl, SPD_KEY_INCOMPLETE,
45 	    &inc)) != 0) {
46 		warnc(ret, "failed to lookup incomplete key");
47 		return (B_FALSE);
48 	}
49 
50 	if (inc != 0x11) {
51 		warnx("incomplete key has unexpected offset: expected %u, "
52 		    "found %u", 0x11, inc);
53 		return (B_FALSE);
54 	}
55 
56 	return (B_TRUE);
57 }
58 
59 static boolean_t
spd_check_single_err(nvlist_t * nvl,const char * key,spd_error_kind_t kind)60 spd_check_single_err(nvlist_t *nvl, const char *key, spd_error_kind_t kind)
61 {
62 	int ret;
63 	nvlist_t *check;
64 	uint32_t code;
65 	char *msg;
66 	boolean_t pass = B_TRUE;
67 
68 	if ((ret = nvlist_lookup_nvlist(nvl, key, &check)) != 0) {
69 		warnc(ret, "failed to lookup key %s in error nvlist", key);
70 		dump_nvlist(nvl, 4);
71 		return (B_FALSE);
72 	}
73 
74 	if ((ret = nvlist_lookup_string(check, SPD_KEY_ERRS_MSG, &msg)) != 0) {
75 		warnc(ret, "missing error message for error key %s", key);
76 		dump_nvlist(check, 6);
77 		pass = B_FALSE;
78 	}
79 
80 	if ((ret = nvlist_lookup_uint32(check, SPD_KEY_ERRS_CODE,
81 	    &code)) != 0) {
82 		warnc(ret, "missing error number for error key %s", key);
83 		dump_nvlist(check, 6);
84 		pass = B_FALSE;
85 	} else if (code != kind) {
86 		warnx("found wrong error kind for error key %s: expected 0x%x, "
87 		    "found 0x%x", key, kind, code);
88 		pass = B_FALSE;
89 	}
90 
91 	nvlist_free(check);
92 	return (pass);
93 }
94 
95 /*
96  * This goes through and checks for a number of error codes being set as
97  * expected. Note, we check that the message exists, but we don't validate its
98  * contents in any way. Because we're using all zero data, we can expect to find
99  * a number of different cases.
100  */
101 static boolean_t
spd_check_misc_errors(nvlist_t * nvl)102 spd_check_misc_errors(nvlist_t *nvl)
103 {
104 	int ret;
105 	nvlist_t *errs;
106 	boolean_t pass = B_TRUE;
107 
108 	if ((ret = nvlist_lookup_nvlist(nvl, SPD_KEY_ERRS, &errs)) != 0) {
109 		warnc(ret, "failed to lookup errors nvlist");
110 		return (B_FALSE);
111 	}
112 
113 	if (!spd_check_single_err(errs, SPD_KEY_MFG_DRAM_MFG_NAME,
114 	    SPD_ERROR_NO_XLATE) ||
115 	    !spd_check_single_err(errs, SPD_KEY_CRC_DDR4_BASE,
116 	    SPD_ERROR_BAD_DATA) ||
117 	    !spd_check_single_err(errs, SPD_KEY_MFG_MOD_PN,
118 	    SPD_ERROR_UNPRINT) ||
119 	    !spd_check_single_err(errs, SPD_KEY_TRCD_MIN, SPD_ERROR_NO_XLATE)) {
120 		pass = B_FALSE;
121 	}
122 
123 
124 	nvlist_free(errs);
125 	return (pass);
126 }
127 
128 static const libjedec_spd_test_t spd_tests[] = {
129 	{ .lst_data = {}, .lst_len = 0, .lst_err = LIBJEDEC_SPD_TOOSHORT,
130 	    .lst_desc = "Invalid SPD Data (zero length)" },
131 	{ .lst_data = { 0x00, 0x10, SPD_DT_DDR_SGRAM, 0x00 }, .lst_len = 4,
132 	    .lst_err = LIBJEDEC_SPD_UNSUP_TYPE, .lst_desc = "Unsupported "
133 	    "SPD type (DDR SGRAM)" },
134 	{ .lst_data = { 0x00, 0x10, 0x42, 0x00 }, .lst_len = 4,
135 	    .lst_err = LIBJEDEC_SPD_UNSUP_TYPE, .lst_desc = "Unknown "
136 	    "SPD type (0x42)" },
137 	{ .lst_data = { 0x00, 0x00, SPD_DT_DDR4_SDRAM, 0x00 }, .lst_len = 4,
138 	    .lst_err = LIBJEDEC_SPD_UNSUP_REV, .lst_desc = "Bad DDR4 "
139 	    "Revision (0x00)" },
140 	{ .lst_data = { 0x00, 0x54, SPD_DT_DDR4_SDRAM, 0x00 }, .lst_len = 4,
141 	    .lst_err = LIBJEDEC_SPD_UNSUP_REV, .lst_desc = "Bad DDR4 "
142 	    "Revision (0x54)" },
143 	{ .lst_data = { 0x00, 0x00, SPD_DT_DDR5_SDRAM, 0x00 }, .lst_len = 4,
144 	    .lst_err = LIBJEDEC_SPD_UNSUP_REV, .lst_desc = "Bad DDR4 "
145 	    "Revision (0x00)" },
146 	{ .lst_data = { 0x00, 0xb2, SPD_DT_DDR5_SDRAM, 0x00 }, .lst_len = 4,
147 	    .lst_err = LIBJEDEC_SPD_UNSUP_REV, .lst_desc = "Bad DDR5 "
148 	    "Revision (0xb2)" },
149 	{ .lst_data = { 0x00, 0x10, SPD_DT_DDR5_SDRAM, 0x00 }, .lst_len = 0xc3,
150 	    .lst_err = LIBJEDEC_SPD_UNSUP_REV, .lst_desc = "Bad DDR5 Common "
151 	    "Revision (0x00)" },
152 	{ .lst_data = { 0x00, 0x10, SPD_DT_DDR4_SDRAM, 0x00 }, .lst_len = 0x10,
153 	    .lst_err = LIBJEDEC_SPD_OK, .lst_desc = "Catch incomplete errors",
154 	    .lst_check = spd_check_short_ddr4 },
155 	{ .lst_data = { 0x00, 0x10, SPD_DT_DDR4_SDRAM, 0x00 }, .lst_len = 0x200,
156 	    .lst_err = LIBJEDEC_SPD_OK, .lst_desc = "Non-fatal parsing errors",
157 	    .lst_check = spd_check_misc_errors },
158 
159 };
160 
161 static boolean_t
libjedec_spd_test(const libjedec_spd_test_t * test)162 libjedec_spd_test(const libjedec_spd_test_t *test)
163 {
164 	nvlist_t *nvl;
165 	spd_error_t err;
166 	boolean_t pass = B_TRUE;
167 
168 	nvl = libjedec_spd(test->lst_data, test->lst_len, &err);
169 	if (err != test->lst_err) {
170 		warnx("found mismatched error: expected 0x%x, found 0x%x",
171 		    test->lst_err, err);
172 		pass = B_FALSE;
173 	}
174 
175 	if (nvl != NULL) {
176 		if (test->lst_err != LIBJEDEC_SPD_OK) {
177 			warnx("expected fatal error (0x%x), but somehow got "
178 			    "an nvlist! Contents:", test->lst_err);
179 			dump_nvlist(nvl, 4);
180 			pass = B_FALSE;
181 		}
182 	} else {
183 		if (test->lst_err == LIBJEDEC_SPD_OK) {
184 			warnx("expected an nvlist_t, but didn't get one: "
185 			    "actual spd_error_t: 0x%x", err);
186 			pass = B_FALSE;
187 		}
188 	}
189 
190 	if (pass && test->lst_check) {
191 		pass = test->lst_check(nvl);
192 	}
193 
194 	return (pass);
195 }
196 
197 int
main(void)198 main(void)
199 {
200 	int ret = EXIT_SUCCESS;
201 
202 	for (size_t i = 0; i < ARRAY_SIZE(spd_tests); i++) {
203 		const libjedec_spd_test_t *test = &spd_tests[i];
204 
205 		if (!libjedec_spd_test(test)) {
206 			(void) fprintf(stderr, "TEST FAILED: %s\n",
207 			    test->lst_desc);
208 			ret = EXIT_FAILURE;
209 		} else {
210 			(void) printf("TEST PASSED: %s\n", test->lst_desc);
211 		}
212 	}
213 
214 	return (ret);
215 }
216