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 2025 Oxide Computer Company
14 */
15
16 /*
17 * libnvme pieces specific to WDC.
18 *
19 * Currently this defines several common log pages that are found in a few
20 * generations of WDC devices such as the SN840 and SN65x. There is also support
21 * for a few of the vendor specific commands in the device.
22 *
23 * Currently there is support for two commands in library form: getting an e6
24 * log and performing a device resize. Because there are a few different
25 * parameters needed to issue the e6 request, we end up structuring it like the
26 * library's other request structures, even though it just uses the vendor
27 * unique commands. We do not use the full field validation structures for this
28 * because a portion of that is used by the vendor unique subsystem. Instead we
29 * manually validate the offset and track fields being set.
30 */
31
32 #include <string.h>
33 #include <sys/sysmacros.h>
34 #include <sys/nvme/wdc.h>
35
36 #include "libnvme_impl.h"
37
38 /*
39 * The amount of time that this command takes appears to somewhat relate to the
40 * size of the overall device and transformations that are going on. This value
41 * is an attempt to get through most resize testing plus a little slack in
42 * all of our testing to date.
43 */
44 static const uint32_t nvme_wdc_resize_timeout = 30;
45
46 /*
47 * We expect a given read of a region of an e6 log to take this amount of time
48 * in seconds.
49 */
50 static const uint32_t nvme_wdc_e6_timeout = 30;
51
52 /*
53 * Timeout for injecting and clearing asserts. We make this generous as assert
54 * injection may take some time.
55 */
56 static const uint32_t nvme_wdc_assert_timeout = 45;
57
58 typedef enum {
59 NVME_WDC_E6_REQ_FIELD_OFFSET = 0,
60 NVME_WDC_E6_REQ_FIELD_LEN
61 } nvme_wdc_e6_req_field_t;
62
63 static bool
nvme_wdc_e6_field_valid_offset(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,uint64_t off,char * msg,size_t msglen)64 nvme_wdc_e6_field_valid_offset(const nvme_field_info_t *field,
65 const nvme_valid_ctrl_data_t *data, uint64_t off, char *msg, size_t msglen)
66 {
67 uint64_t max;
68
69 if ((off % NVME_DWORD_SIZE) != 0) {
70 (void) snprintf(msg, msglen, "field %s (%s) value 0x%" PRIx64
71 "must be %u-byte aligned", field->nlfi_human,
72 field->nlfi_spec, off, NVME_DWORD_SIZE);
73 return (false);
74 }
75
76 max = (uint64_t)UINT32_MAX << NVME_DWORD_SHIFT;
77 return (nvme_field_range_check(field, 0, max, msg, msglen, off));
78 }
79
80 const nvme_field_info_t nvme_wdc_e6_req_fields[] = {
81 [NVME_WDC_E6_REQ_FIELD_OFFSET] = {
82 .nlfi_vers = &nvme_vers_1v0,
83 .nlfi_valid = nvme_wdc_e6_field_valid_offset,
84 .nlfi_spec = "offset",
85 .nlfi_human = "e6 log offset",
86 .nlfi_def_req = true,
87 .nlfi_def_allow = true
88 },
89 /*
90 * Note there is no validation of this field because we rely on the
91 * underlying vendor unique command output length to do so.
92 */
93 [NVME_WDC_E6_REQ_FIELD_LEN] = {
94 .nlfi_vers = &nvme_vers_1v0,
95 .nlfi_spec = "length",
96 .nlfi_human = "data transfer length",
97 .nlfi_def_req = true,
98 .nlfi_def_allow = true
99 },
100 };
101
102 static bool
nvme_wdc_log_dev_mgmt_var_len(uint64_t * outp,const void * data,size_t len)103 nvme_wdc_log_dev_mgmt_var_len(uint64_t *outp, const void *data, size_t len)
104 {
105 wdc_vsd_t vsd;
106
107 if (len < sizeof (vsd)) {
108 return (false);
109 }
110
111 (void) memcpy(&vsd, data, sizeof (vsd));
112 *outp = vsd.vsd_len;
113 return (true);
114 }
115
116 static bool
nvme_wdc_log_samples_var_len(uint64_t * outp,const void * data,size_t len)117 nvme_wdc_log_samples_var_len(uint64_t *outp, const void *data, size_t len)
118 {
119 uint32_t nsamp;
120
121 if (len < sizeof (uint32_t)) {
122 return (false);
123 }
124
125 (void) memcpy(&nsamp, data, sizeof (uint32_t));
126 *outp = (uint64_t)nsamp * sizeof (uint32_t);
127 return (true);
128 }
129
130 static bool
nvme_wdc_sn840_fw_act_var_len(uint64_t * outp,const void * data,size_t len)131 nvme_wdc_sn840_fw_act_var_len(uint64_t *outp, const void *data, size_t len)
132 {
133 wdc_vul_sn840_fw_act_hdr_t hdr;
134
135 if (len < sizeof (wdc_vul_sn840_fw_act_hdr_t)) {
136 return (false);
137 }
138
139 (void) memcpy(&hdr, data, sizeof (uint32_t));
140 *outp = (uint64_t)hdr.fah_nent * hdr.fah_entlen;
141 return (true);
142 }
143
144 static const nvme_log_page_info_t wdc_sn840_log_eol = {
145 .nlpi_short = "wdc/eol",
146 .nlpi_human = "EOL",
147 .nlpi_lid = WDC_SN840_LOG_EOL,
148 .nlpi_csi = NVME_CSI_NVM,
149 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
150 .nlpi_source = NVME_LOG_DISC_S_DB,
151 .nlpi_scope = NVME_LOG_SCOPE_NVM,
152 .nlpi_len = sizeof (wdc_vul_sn840_eol_t)
153 };
154
155 static const nvme_log_page_info_t wdc_sn840_log_devmgmt = {
156 .nlpi_short = "wdc/devmgmt",
157 .nlpi_human = "Device Manageability",
158 .nlpi_lid = WDC_SN840_LOG_DEV_MANAGE,
159 .nlpi_csi = NVME_CSI_NVM,
160 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
161 .nlpi_source = NVME_LOG_DISC_S_DB,
162 .nlpi_scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NS,
163 .nlpi_len = sizeof (wdc_vsd_t),
164 .nlpi_var_func = nvme_wdc_log_dev_mgmt_var_len
165 };
166
167 static const nvme_log_page_info_t wdc_sn840_log_pciesi = {
168 .nlpi_short = "wdc/pciesi",
169 .nlpi_human = "PCIe Signal Integrity",
170 .nlpi_lid = WDC_SN840_LOG_PCIE_SI,
171 .nlpi_csi = NVME_CSI_NVM,
172 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
173 .nlpi_source = NVME_LOG_DISC_S_DB,
174 .nlpi_disc = NVME_LOG_DISC_F_NEED_LSP,
175 .nlpi_scope = NVME_LOG_SCOPE_CTRL
176 };
177
178 static const nvme_log_page_info_t wdc_sn840_log_power = {
179 .nlpi_short = "wdc/power",
180 .nlpi_human = "Power Samples",
181 .nlpi_lid = WDC_SN840_LOG_POWER,
182 .nlpi_csi = NVME_CSI_NVM,
183 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
184 .nlpi_source = NVME_LOG_DISC_S_DB,
185 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
186 .nlpi_len = sizeof (uint32_t),
187 .nlpi_var_func = nvme_wdc_log_samples_var_len
188 };
189
190 static const nvme_log_page_info_t wdc_sn840_log_temp = {
191 .nlpi_short = "wdc/temp",
192 .nlpi_human = "Temperature Samples",
193 .nlpi_lid = WDC_SN840_LOG_TEMP,
194 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
195 .nlpi_source = NVME_LOG_DISC_S_DB,
196 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
197 .nlpi_len = sizeof (uint32_t),
198 .nlpi_var_func = nvme_wdc_log_samples_var_len
199 };
200
201 static const nvme_log_page_info_t wdc_sn840_log_fwact = {
202 .nlpi_short = "wdc/fwact",
203 .nlpi_human = "Firmware Activation",
204 .nlpi_lid = WDC_SN840_LOG_FW_ACT,
205 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
206 .nlpi_source = NVME_LOG_DISC_S_DB,
207 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
208 .nlpi_len = sizeof (wdc_vul_sn840_fw_act_hdr_t),
209 .nlpi_var_func = nvme_wdc_sn840_fw_act_var_len
210 };
211
212 static const nvme_log_page_info_t wdc_sn840_log_cdds = {
213 .nlpi_short = "wdc/ccds",
214 .nlpi_human = "CCDS Build Information",
215 .nlpi_lid = WDC_SN840_LOG_CCDS,
216 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
217 .nlpi_source = NVME_LOG_DISC_S_DB,
218 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
219 .nlpi_len = sizeof (wdc_vul_sn840_ccds_info_t)
220 };
221
222 static const nvme_log_page_info_t *wdc_sn840_log_pages[] = {
223 &wdc_sn840_log_eol, &wdc_sn840_log_devmgmt, &wdc_sn840_log_pciesi,
224 &wdc_sn840_log_power, &wdc_sn840_log_temp, &wdc_sn840_log_fwact,
225 &wdc_sn840_log_cdds
226 };
227
228 static const nvme_log_page_info_t wdc_sn65x_log_power = {
229 .nlpi_short = "wdc/power",
230 .nlpi_human = "Power Samples",
231 .nlpi_lid = WDC_SN65X_LOG_POWER,
232 .nlpi_csi = NVME_CSI_NVM,
233 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
234 .nlpi_source = NVME_LOG_DISC_S_DB,
235 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
236 .nlpi_len = sizeof (uint32_t),
237 .nlpi_var_func = nvme_wdc_log_samples_var_len
238 };
239
240 static const nvme_log_page_info_t wdc_sn65x_log_temp = {
241 .nlpi_short = "wdc/temp",
242 .nlpi_human = "Temperature Samples",
243 .nlpi_lid = WDC_SN65X_LOG_TEMP,
244 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
245 .nlpi_source = NVME_LOG_DISC_S_DB,
246 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
247 .nlpi_len = sizeof (uint32_t),
248 .nlpi_var_func = nvme_wdc_log_samples_var_len
249 };
250
251 static const nvme_log_page_info_t wdc_sn65x_log_cusmart = {
252 .nlpi_short = "wdc/cusmart",
253 .nlpi_human = "Customer Unique SMART",
254 .nlpi_lid = WDC_SN65X_LOG_UNIQUE_SMART,
255 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
256 .nlpi_source = NVME_LOG_DISC_S_DB,
257 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
258 .nlpi_len = sizeof (wdc_vul_sn65x_smart_t)
259 };
260
261 static const nvme_log_page_info_t *wdc_sn65x_log_pages[] = {
262 &ocp_log_smart, &wdc_sn65x_log_power, &wdc_sn65x_log_temp,
263 &wdc_sn65x_log_cusmart
264 };
265
266
267 /*
268 * Currently these commands are shared across the SN840, SN650, and SN655.
269 * This will likely need to be split up and redone when we end up with more
270 * device-specific commands that aren't shared across controller generations.
271 * When we get to that we should choose whether we want to redefine the vuc like
272 * we have with log pages or if we should move to a shared structure that is
273 * incorporated as an array of pointers.
274 */
275 static const nvme_vuc_disc_t wdc_sn840_sn65x_vuc[] = { {
276 .nvd_short = "wdc/resize",
277 .nvd_desc = "drive resize",
278 .nvd_opc = WDC_VUC_RESIZE_OPC,
279 .nvd_impact = NVME_VUC_DISC_IMPACT_DATA | NVME_VUC_DISC_IMPACT_NS,
280 .nvd_dt = NVME_VUC_DISC_IO_NONE,
281 .nvd_lock = NVME_VUC_DISC_LOCK_WRITE
282 }, {
283 .nvd_short = "wdc/e6dump",
284 .nvd_desc = "dump e6 diagnostic data",
285 .nvd_opc = WDC_VUC_E6_DUMP_OPC,
286 .nvd_dt = NVME_VUC_DISC_IO_OUTPUT,
287 .nvd_lock = NVME_VUC_DISC_LOCK_READ
288 }, {
289 .nvd_short = "wdc/clear-assert",
290 .nvd_desc = "clear internal drive assertion",
291 .nvd_opc = WDC_VUC_ASSERT_OPC,
292 .nvd_dt = NVME_VUC_DISC_IO_NONE,
293 .nvd_lock = NVME_VUC_DISC_LOCK_NONE
294 }, {
295 /*
296 * It's hard to come up with a good impact statement from this. It will
297 * cause I/O to fail but may or may not cause issues with data.
298 */
299 .nvd_short = "wdc/inject-assert",
300 .nvd_desc = "inject internal drive assertion",
301 .nvd_opc = WDC_VUC_ASSERT_OPC,
302 .nvd_dt = NVME_VUC_DISC_IO_NONE,
303 .nvd_lock = NVME_VUC_DISC_LOCK_WRITE
304 } };
305
306 static const nvme_vsd_ident_t wdc_sn840_idents[] = {
307 {
308 .nvdi_vid = WDC_PCI_VID,
309 .nvdi_did = WDC_SN840_DID,
310 .nvdi_human = "WDC Ultrastar DC SN840",
311 }
312 };
313
314 const nvme_vsd_t wdc_sn840 = {
315 .nvd_ident = wdc_sn840_idents,
316 .nvd_nident = ARRAY_SIZE(wdc_sn840_idents),
317 .nvd_logs = wdc_sn840_log_pages,
318 .nvd_nlogs = ARRAY_SIZE(wdc_sn840_log_pages),
319 .nvd_vuc = wdc_sn840_sn65x_vuc,
320 .nvd_nvuc = ARRAY_SIZE(wdc_sn840_sn65x_vuc)
321 };
322
323 static const nvme_vsd_ident_t wdc_sn65x_idents[] = {
324 {
325 .nvdi_vid = WDC_PCI_VID,
326 .nvdi_did = WDC_SN650_DID,
327 .nvdi_human = "WDC Ultrastar DC SN650",
328 }, {
329 .nvdi_vid = WDC_PCI_VID,
330 .nvdi_did = WDC_SN655_DID,
331 .nvdi_human = "WDC Ultrastar DC SN655",
332 }
333 };
334
335 const nvme_vsd_t wdc_sn65x = {
336 .nvd_ident = wdc_sn65x_idents,
337 .nvd_nident = ARRAY_SIZE(wdc_sn65x_idents),
338 .nvd_logs = wdc_sn65x_log_pages,
339 .nvd_nlogs = ARRAY_SIZE(wdc_sn65x_log_pages),
340 .nvd_vuc = wdc_sn840_sn65x_vuc,
341 .nvd_nvuc = ARRAY_SIZE(wdc_sn840_sn65x_vuc)
342 };
343
344 static const nvme_vsd_ident_t wdc_sn861_idents[] = {
345 {
346 .nvdi_vid = WDC_PCI_VID,
347 .nvdi_did = WDC_SN861_DID,
348 .nvdi_human = "WDC Ultrastar DC SN861",
349 }
350 };
351
352 static const nvme_log_page_info_t *wdc_sn861_log_pages[] = {
353 &ocp_log_smart, &ocp_log_errrec, &ocp_log_fwact, &ocp_log_lat,
354 &ocp_log_devcap, &ocp_log_unsup
355 };
356
357 const nvme_vsd_t wdc_sn861 = {
358 .nvd_ident = wdc_sn861_idents,
359 .nvd_nident = ARRAY_SIZE(wdc_sn861_idents),
360 .nvd_logs = wdc_sn861_log_pages,
361 .nvd_nlogs = ARRAY_SIZE(wdc_sn861_log_pages),
362 };
363
364 static nvme_vuc_req_t *
nvme_wdc_resize_vuc(nvme_ctrl_t * ctrl,uint8_t subcmd,uint32_t gib)365 nvme_wdc_resize_vuc(nvme_ctrl_t *ctrl, uint8_t subcmd, uint32_t gib)
366 {
367 nvme_vuc_req_t *req = NULL;
368 uint32_t cdw12 = WDC_VUC_RESIZE_CMD | ((uint32_t)subcmd << 8);
369
370 if (!nvme_vendor_vuc_supported(ctrl, "wdc/resize")) {
371 return (false);
372 }
373
374 if (!nvme_vuc_req_init(ctrl, &req)) {
375 return (false);
376 }
377
378 if (!nvme_vuc_req_set_opcode(req, WDC_VUC_RESIZE_OPC) ||
379 !nvme_vuc_req_set_cdw12(req, cdw12) ||
380 !nvme_vuc_req_set_cdw13(req, gib) ||
381 !nvme_vuc_req_set_timeout(req, nvme_wdc_resize_timeout)) {
382 nvme_vuc_req_fini(req);
383 return (false);
384 }
385
386 return (req);
387 }
388
389 bool
nvme_wdc_resize_get(nvme_ctrl_t * ctrl,uint32_t * gbp)390 nvme_wdc_resize_get(nvme_ctrl_t *ctrl, uint32_t *gbp)
391 {
392 nvme_vuc_req_t *vuc;
393
394 if (gbp == NULL) {
395 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
396 "encountered invalid uint32_t pointer: %p", gbp));
397 }
398
399 if ((vuc = nvme_wdc_resize_vuc(ctrl, WDC_VUC_RESIZE_SUB_GET, 0)) ==
400 NULL) {
401 return (false);
402 }
403
404 if (!nvme_vuc_req_exec(vuc)) {
405 nvme_vuc_req_fini(vuc);
406 return (false);
407 }
408
409 if (!nvme_vuc_req_get_cdw0(vuc, gbp)) {
410 nvme_vuc_req_fini(vuc);
411 return (false);
412 }
413
414 return (nvme_ctrl_success(ctrl));
415 }
416
417 bool
nvme_wdc_resize_set(nvme_ctrl_t * ctrl,uint32_t gb)418 nvme_wdc_resize_set(nvme_ctrl_t *ctrl, uint32_t gb)
419 {
420 nvme_vuc_req_t *vuc;
421
422 if ((vuc = nvme_wdc_resize_vuc(ctrl, WDC_VUC_RESIZE_SUB_SET, gb)) ==
423 NULL) {
424 return (false);
425 }
426
427 if (!nvme_vuc_req_set_impact(vuc, NVME_VUC_DISC_IMPACT_DATA |
428 NVME_VUC_DISC_IMPACT_NS)) {
429 nvme_vuc_req_fini(vuc);
430 return (false);
431 }
432
433 if (!nvme_vuc_req_exec(vuc)) {
434 nvme_vuc_req_fini(vuc);
435 return (false);
436 }
437
438 nvme_vuc_req_fini(vuc);
439 return (nvme_ctrl_success(ctrl));
440 }
441
442 void
nvme_wdc_e6_req_fini(nvme_wdc_e6_req_t * req)443 nvme_wdc_e6_req_fini(nvme_wdc_e6_req_t *req)
444 {
445 if (req == NULL) {
446 return;
447 }
448
449 nvme_vuc_req_fini(req->wer_vuc);
450 req->wer_vuc = NULL;
451 free(req);
452 }
453
454 bool
nvme_wdc_e6_req_init(nvme_ctrl_t * ctrl,nvme_wdc_e6_req_t ** reqp)455 nvme_wdc_e6_req_init(nvme_ctrl_t *ctrl, nvme_wdc_e6_req_t **reqp)
456 {
457 nvme_wdc_e6_req_t *req;
458
459 if (reqp == NULL) {
460 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
461 "encountered invalid nvme_commit_req_t output pointer: %p",
462 reqp));
463 }
464
465 if (!nvme_vendor_vuc_supported(ctrl, "wdc/e6dump")) {
466 return (false);
467 }
468
469 req = calloc(1, sizeof (nvme_wdc_e6_req_t));
470 if (req == NULL) {
471 int e = errno;
472 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
473 "allocate memory for a new nvme_wdc_e6_req_t: %s",
474 strerror(e)));
475 }
476
477 if (!nvme_vuc_req_init(ctrl, &req->wer_vuc)) {
478 nvme_wdc_e6_req_fini(req);
479 return (false);
480 }
481
482 /*
483 * The documentation suggests we must explicitly set the mode in cdw12
484 * to zero. While that should be the default, we do anyways.
485 */
486 if (!nvme_vuc_req_set_opcode(req->wer_vuc, WDC_VUC_E6_DUMP_OPC) ||
487 !nvme_vuc_req_set_cdw12(req->wer_vuc, 0) ||
488 !nvme_vuc_req_set_timeout(req->wer_vuc, nvme_wdc_e6_timeout)) {
489 nvme_wdc_e6_req_fini(req);
490 return (false);
491 }
492
493 for (size_t i = 0; i < ARRAY_SIZE(nvme_wdc_e6_req_fields); i++) {
494 if (nvme_wdc_e6_req_fields[i].nlfi_def_req) {
495 req->wer_need |= 1 << i;
496 }
497 }
498
499 *reqp = req;
500 return (nvme_ctrl_success(ctrl));
501 }
502
503 static void
nvme_wdc_e6_req_set_need(nvme_wdc_e6_req_t * req,nvme_wdc_e6_req_field_t field)504 nvme_wdc_e6_req_set_need(nvme_wdc_e6_req_t *req,
505 nvme_wdc_e6_req_field_t field)
506 {
507 req->wer_need |= 1 << field;
508 }
509
510 static void
nvme_wdc_e6_req_clear_need(nvme_wdc_e6_req_t * req,nvme_wdc_e6_req_field_t field)511 nvme_wdc_e6_req_clear_need(nvme_wdc_e6_req_t *req,
512 nvme_wdc_e6_req_field_t field)
513 {
514 req->wer_need &= ~(1 << field);
515 }
516
517 static const nvme_field_check_t nvme_wdc_e6_check_off = {
518 nvme_wdc_e6_req_fields, NVME_WDC_E6_REQ_FIELD_OFFSET,
519 NVME_ERR_WDC_E6_OFFSET_RANGE, 0, 0
520 };
521
522 bool
nvme_wdc_e6_req_set_offset(nvme_wdc_e6_req_t * req,uint64_t off)523 nvme_wdc_e6_req_set_offset(nvme_wdc_e6_req_t *req, uint64_t off)
524 {
525 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl;
526 uint32_t ndw;
527
528 if (!nvme_field_check_one(ctrl, off, "e6 dump", &nvme_wdc_e6_check_off,
529 0)) {
530 return (false);
531 }
532
533 ndw = off >> 2;
534 if (!nvme_vuc_req_set_cdw13(req->wer_vuc, ndw)) {
535 return (false);
536 }
537
538 nvme_wdc_e6_req_clear_need(req, NVME_WDC_E6_REQ_FIELD_OFFSET);
539 return (nvme_ctrl_success(ctrl));
540 }
541
542 bool
nvme_wdc_e6_req_set_output(nvme_wdc_e6_req_t * req,void * buf,size_t len)543 nvme_wdc_e6_req_set_output(nvme_wdc_e6_req_t *req, void *buf, size_t len)
544 {
545 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl;
546
547 /*
548 * The set output validation handling takes care of all the actual
549 * normal field validation work that we need.
550 */
551 if (!nvme_vuc_req_set_output(req->wer_vuc, buf, len)) {
552 return (false);
553 }
554
555 nvme_wdc_e6_req_clear_need(req, NVME_WDC_E6_REQ_FIELD_LEN);
556 return (nvme_ctrl_success(ctrl));
557 }
558
559 bool
nvme_wdc_e6_req_clear_output(nvme_wdc_e6_req_t * req)560 nvme_wdc_e6_req_clear_output(nvme_wdc_e6_req_t *req)
561 {
562 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl;
563
564 if (!nvme_vuc_req_clear_output(req->wer_vuc)) {
565 return (false);
566 }
567
568 nvme_wdc_e6_req_set_need(req, NVME_WDC_E6_REQ_FIELD_LEN);
569 return (nvme_ctrl_success(ctrl));
570 }
571
572 bool
nvme_wdc_e6_req_exec(nvme_wdc_e6_req_t * req)573 nvme_wdc_e6_req_exec(nvme_wdc_e6_req_t *req)
574 {
575 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl;
576
577 if (req->wer_need != 0) {
578 return (nvme_field_miss_err(ctrl, nvme_wdc_e6_req_fields,
579 ARRAY_SIZE(nvme_wdc_e6_req_fields),
580 NVME_ERR_WDC_E6_REQ_MISSING_FIELDS, "wdc e6",
581 req->wer_need));
582 }
583
584 if (!nvme_vuc_req_exec(req->wer_vuc)) {
585 return (false);
586 }
587
588 return (nvme_ctrl_success(ctrl));
589 }
590
591 static bool
nvme_wdc_assert_common(nvme_ctrl_t * ctrl,uint32_t subcmd)592 nvme_wdc_assert_common(nvme_ctrl_t *ctrl, uint32_t subcmd)
593 {
594 nvme_vuc_req_t *req = NULL;
595 const char *name = subcmd == WDC_VUC_ASSERT_SUB_CLEAR ?
596 "wdc/clear-assert" : "wdc/inject-assert";
597 uint32_t cdw12 = WDC_VUC_ASSERT_CMD | (subcmd << 8);
598
599 if (!nvme_vendor_vuc_supported(ctrl, name)) {
600 return (false);
601 }
602
603 if (!nvme_vuc_req_init(ctrl, &req)) {
604 return (false);
605 }
606
607 if (!nvme_vuc_req_set_opcode(req, WDC_VUC_ASSERT_OPC) ||
608 !nvme_vuc_req_set_cdw12(req, cdw12) ||
609 !nvme_vuc_req_set_timeout(req, nvme_wdc_assert_timeout) ||
610 !nvme_vuc_req_exec(req)) {
611 nvme_vuc_req_fini(req);
612 return (false);
613 }
614
615 nvme_vuc_req_fini(req);
616 return (nvme_ctrl_success(ctrl));
617 }
618
619 bool
nvme_wdc_assert_clear(nvme_ctrl_t * ctrl)620 nvme_wdc_assert_clear(nvme_ctrl_t *ctrl)
621 {
622 return (nvme_wdc_assert_common(ctrl, WDC_VUC_ASSERT_SUB_CLEAR));
623 }
624
625 bool
nvme_wdc_assert_inject(nvme_ctrl_t * ctrl)626 nvme_wdc_assert_inject(nvme_ctrl_t *ctrl)
627 {
628 return (nvme_wdc_assert_common(ctrl, WDC_VUC_ASSERT_SUB_INJECT));
629 }
630