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