xref: /illumos-gate/usr/src/lib/libnvme/common/libnvme_wdc.c (revision 0886dcadf4b2cd677c3b944167f0d16ccb243616)
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  * 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
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
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
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
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_pages[] = { {
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 	.nlpi_short = "wdc/devmgmt",
155 	.nlpi_human = "Device Manageability",
156 	.nlpi_lid = WDC_SN840_LOG_DEV_MANAGE,
157 	.nlpi_csi = NVME_CSI_NVM,
158 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
159 	.nlpi_source = NVME_LOG_DISC_S_DB,
160 	.nlpi_scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NS,
161 	.nlpi_len = sizeof (wdc_vsd_t),
162 	.nlpi_var_func = nvme_wdc_log_dev_mgmt_var_len
163 }, {
164 	.nlpi_short = "wdc/pciesi",
165 	.nlpi_human = "PCIe Signal Integrity",
166 	.nlpi_lid = WDC_SN840_LOG_PCIE_SI,
167 	.nlpi_csi = NVME_CSI_NVM,
168 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
169 	.nlpi_source = NVME_LOG_DISC_S_DB,
170 	.nlpi_disc = NVME_LOG_DISC_F_NEED_LSP,
171 	.nlpi_scope = NVME_LOG_SCOPE_CTRL
172 }, {
173 	.nlpi_short = "wdc/power",
174 	.nlpi_human = "Power Samples",
175 	.nlpi_lid = WDC_SN840_LOG_POWER,
176 	.nlpi_csi = NVME_CSI_NVM,
177 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
178 	.nlpi_source = NVME_LOG_DISC_S_DB,
179 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
180 	.nlpi_len = sizeof (uint32_t),
181 	.nlpi_var_func = nvme_wdc_log_samples_var_len
182 }, {
183 	.nlpi_short = "wdc/temp",
184 	.nlpi_human = "Temperature Samples",
185 	.nlpi_lid = WDC_SN840_LOG_TEMP,
186 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
187 	.nlpi_source = NVME_LOG_DISC_S_DB,
188 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
189 	.nlpi_len = sizeof (uint32_t),
190 	.nlpi_var_func = nvme_wdc_log_samples_var_len
191 }, {
192 	.nlpi_short = "wdc/fwact",
193 	.nlpi_human = "Firmware Activation",
194 	.nlpi_lid = WDC_SN840_LOG_FW_ACT,
195 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
196 	.nlpi_source = NVME_LOG_DISC_S_DB,
197 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
198 	.nlpi_len = sizeof (wdc_vul_sn840_fw_act_hdr_t),
199 	.nlpi_var_func = nvme_wdc_sn840_fw_act_var_len
200 }, {
201 	.nlpi_short = "wdc/ccds",
202 	.nlpi_human = "CCDS Build Information",
203 	.nlpi_lid = WDC_SN840_LOG_CCDS,
204 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
205 	.nlpi_source = NVME_LOG_DISC_S_DB,
206 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
207 	.nlpi_len = sizeof (wdc_vul_sn840_ccds_info_t)
208 } };
209 
210 static const nvme_log_page_info_t wdc_sn65x_log_pages[] = { {
211 	.nlpi_short = "wdc/power",
212 	.nlpi_human = "Power Samples",
213 	.nlpi_lid = WDC_SN65X_LOG_POWER,
214 	.nlpi_csi = NVME_CSI_NVM,
215 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
216 	.nlpi_source = NVME_LOG_DISC_S_DB,
217 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
218 	.nlpi_len = sizeof (uint32_t),
219 	.nlpi_var_func = nvme_wdc_log_samples_var_len
220 }, {
221 	.nlpi_short = "wdc/temp",
222 	.nlpi_human = "Temperature Samples",
223 	.nlpi_lid = WDC_SN65X_LOG_TEMP,
224 	.nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC,
225 	.nlpi_source = NVME_LOG_DISC_S_DB,
226 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
227 	.nlpi_len = sizeof (uint32_t),
228 	.nlpi_var_func = nvme_wdc_log_samples_var_len
229 }, {
230 	.nlpi_short = "wdc/cusmart",
231 	.nlpi_human = "Customer Unique SMART",
232 	.nlpi_lid = WDC_SN65X_LOG_UNIQUE_SMART,
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 (wdc_vul_sn65x_smart_t)
237 } };
238 
239 /*
240  * Currently these commands are shared across the SN840, SN650, and SN655.
241  * This will likely need to be split up and redone when we end up with more
242  * device-specific commands that aren't shared across controller generations.
243  * When we get to that we should choose whether we want to redefine the vuc like
244  * we have with log pages or if we should move to a shared structure that is
245  * incorporated as an array of pointers.
246  */
247 static const nvme_vuc_disc_t wdc_sn840_sn65x_vuc[] = { {
248 	.nvd_short = "wdc/resize",
249 	.nvd_desc = "drive resize",
250 	.nvd_opc = WDC_VUC_RESIZE_OPC,
251 	.nvd_impact = NVME_VUC_DISC_IMPACT_DATA | NVME_VUC_DISC_IMPACT_NS,
252 	.nvd_dt = NVME_VUC_DISC_IO_NONE,
253 	.nvd_lock = NVME_VUC_DISC_LOCK_WRITE
254 }, {
255 	.nvd_short = "wdc/e6dump",
256 	.nvd_desc = "dump e6 diagnostic data",
257 	.nvd_opc = WDC_VUC_E6_DUMP_OPC,
258 	.nvd_dt = NVME_VUC_DISC_IO_OUTPUT,
259 	.nvd_lock = NVME_VUC_DISC_LOCK_READ
260 }, {
261 	.nvd_short = "wdc/clear-assert",
262 	.nvd_desc = "clear internal drive assertion",
263 	.nvd_opc = WDC_VUC_ASSERT_OPC,
264 	.nvd_dt = NVME_VUC_DISC_IO_NONE,
265 	.nvd_lock = NVME_VUC_DISC_LOCK_NONE
266 }, {
267 	/*
268 	 * It's hard to come up with a good impact statement from this. It will
269 	 * cause I/O to fail but may or may not cause issues with data.
270 	 */
271 	.nvd_short = "wdc/inject-assert",
272 	.nvd_desc = "inject internal drive assertion",
273 	.nvd_opc = WDC_VUC_ASSERT_OPC,
274 	.nvd_dt = NVME_VUC_DISC_IO_NONE,
275 	.nvd_lock = NVME_VUC_DISC_LOCK_WRITE
276 } };
277 
278 const nvme_vsd_t wdc_sn840 = {
279 	.nvd_vid = WDC_PCI_VID,
280 	.nvd_did = WDC_SN840_DID,
281 	.nvd_human = "WDC Ultrastar DC SN840",
282 	.nvd_logs = wdc_sn840_log_pages,
283 	.nvd_nlogs = ARRAY_SIZE(wdc_sn840_log_pages),
284 	.nvd_vuc = wdc_sn840_sn65x_vuc,
285 	.nvd_nvuc = ARRAY_SIZE(wdc_sn840_sn65x_vuc)
286 };
287 
288 const nvme_vsd_t wdc_sn650 = {
289 	.nvd_vid = WDC_PCI_VID,
290 	.nvd_did = WDC_SN650_DID,
291 	.nvd_human = "WDC Ultrastar DC SN650",
292 	.nvd_logs = wdc_sn65x_log_pages,
293 	.nvd_nlogs = ARRAY_SIZE(wdc_sn65x_log_pages),
294 	.nvd_vuc = wdc_sn840_sn65x_vuc,
295 	.nvd_nvuc = ARRAY_SIZE(wdc_sn840_sn65x_vuc)
296 };
297 
298 const nvme_vsd_t wdc_sn655 = {
299 	.nvd_vid = WDC_PCI_VID,
300 	.nvd_did = WDC_SN655_DID,
301 	.nvd_human = "WDC Ultrastar DC SN655",
302 	.nvd_logs = wdc_sn65x_log_pages,
303 	.nvd_nlogs = ARRAY_SIZE(wdc_sn65x_log_pages),
304 	.nvd_vuc = wdc_sn840_sn65x_vuc,
305 	.nvd_nvuc = ARRAY_SIZE(wdc_sn840_sn65x_vuc)
306 };
307 
308 static nvme_vuc_req_t *
309 nvme_wdc_resize_vuc(nvme_ctrl_t *ctrl, uint8_t subcmd, uint32_t gib)
310 {
311 	nvme_vuc_req_t *req = NULL;
312 	uint32_t cdw12 = WDC_VUC_RESIZE_CMD | ((uint32_t)subcmd << 8);
313 
314 	if (!nvme_vendor_vuc_supported(ctrl, "wdc/resize")) {
315 		return (false);
316 	}
317 
318 	if (!nvme_vuc_req_init(ctrl, &req)) {
319 		return (false);
320 	}
321 
322 	if (!nvme_vuc_req_set_opcode(req, WDC_VUC_RESIZE_OPC) ||
323 	    !nvme_vuc_req_set_cdw12(req, cdw12) ||
324 	    !nvme_vuc_req_set_cdw13(req, gib) ||
325 	    !nvme_vuc_req_set_timeout(req, nvme_wdc_resize_timeout)) {
326 		nvme_vuc_req_fini(req);
327 		return (false);
328 	}
329 
330 	return (req);
331 }
332 
333 bool
334 nvme_wdc_resize_get(nvme_ctrl_t *ctrl, uint32_t *gbp)
335 {
336 	nvme_vuc_req_t *vuc;
337 
338 	if (gbp == NULL) {
339 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
340 		    "encountered invalid uint32_t pointer: %p", gbp));
341 	}
342 
343 	if ((vuc = nvme_wdc_resize_vuc(ctrl, WDC_VUC_RESIZE_SUB_GET, 0)) ==
344 	    NULL) {
345 		return (false);
346 	}
347 
348 	if (!nvme_vuc_req_exec(vuc)) {
349 		nvme_vuc_req_fini(vuc);
350 		return (false);
351 	}
352 
353 	if (!nvme_vuc_req_get_cdw0(vuc, gbp)) {
354 		nvme_vuc_req_fini(vuc);
355 		return (false);
356 	}
357 
358 	return (nvme_ctrl_success(ctrl));
359 }
360 
361 bool
362 nvme_wdc_resize_set(nvme_ctrl_t *ctrl, uint32_t gb)
363 {
364 	nvme_vuc_req_t *vuc;
365 
366 	if ((vuc = nvme_wdc_resize_vuc(ctrl, WDC_VUC_RESIZE_SUB_SET, gb)) ==
367 	    NULL) {
368 		return (false);
369 	}
370 
371 	if (!nvme_vuc_req_set_impact(vuc, NVME_VUC_DISC_IMPACT_DATA |
372 	    NVME_VUC_DISC_IMPACT_NS)) {
373 		nvme_vuc_req_fini(vuc);
374 		return (false);
375 	}
376 
377 	if (!nvme_vuc_req_exec(vuc)) {
378 		nvme_vuc_req_fini(vuc);
379 		return (false);
380 	}
381 
382 	nvme_vuc_req_fini(vuc);
383 	return (nvme_ctrl_success(ctrl));
384 }
385 
386 void
387 nvme_wdc_e6_req_fini(nvme_wdc_e6_req_t *req)
388 {
389 	if (req == NULL) {
390 		return;
391 	}
392 
393 	nvme_vuc_req_fini(req->wer_vuc);
394 	req->wer_vuc = NULL;
395 	free(req);
396 }
397 
398 bool
399 nvme_wdc_e6_req_init(nvme_ctrl_t *ctrl, nvme_wdc_e6_req_t **reqp)
400 {
401 	nvme_wdc_e6_req_t *req;
402 
403 	if (reqp == NULL) {
404 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
405 		    "encountered invalid nvme_commit_req_t output pointer: %p",
406 		    reqp));
407 	}
408 
409 	if (!nvme_vendor_vuc_supported(ctrl, "wdc/e6dump")) {
410 		return (false);
411 	}
412 
413 	req = calloc(1, sizeof (nvme_wdc_e6_req_t));
414 	if (req == NULL) {
415 		int e = errno;
416 		return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
417 		    "allocate memory for a new nvme_wdc_e6_req_t: %s",
418 		    strerror(e)));
419 	}
420 
421 	if (!nvme_vuc_req_init(ctrl, &req->wer_vuc)) {
422 		nvme_wdc_e6_req_fini(req);
423 		return (false);
424 	}
425 
426 	/*
427 	 * The documentation suggests we must explicitly set the mode in cdw12
428 	 * to zero. While that should be the default, we do anyways.
429 	 */
430 	if (!nvme_vuc_req_set_opcode(req->wer_vuc, WDC_VUC_E6_DUMP_OPC) ||
431 	    !nvme_vuc_req_set_cdw12(req->wer_vuc, 0) ||
432 	    !nvme_vuc_req_set_timeout(req->wer_vuc, nvme_wdc_e6_timeout)) {
433 		nvme_wdc_e6_req_fini(req);
434 		return (false);
435 	}
436 
437 	for (size_t i = 0; i < ARRAY_SIZE(nvme_wdc_e6_req_fields); i++) {
438 		if (nvme_wdc_e6_req_fields[i].nlfi_def_req) {
439 			req->wer_need |= 1 << i;
440 		}
441 	}
442 
443 	*reqp = req;
444 	return (nvme_ctrl_success(ctrl));
445 }
446 
447 static void
448 nvme_wdc_e6_req_clear_need(nvme_wdc_e6_req_t *req,
449     nvme_wdc_e6_req_field_t field)
450 {
451 	req->wer_need &= ~(1 << field);
452 }
453 
454 static const nvme_field_check_t nvme_wdc_e6_check_off = {
455 	nvme_wdc_e6_req_fields, NVME_WDC_E6_REQ_FIELD_OFFSET,
456 	NVME_ERR_WDC_E6_OFFSET_RANGE, 0, 0
457 };
458 
459 bool
460 nvme_wdc_e6_req_set_offset(nvme_wdc_e6_req_t *req, uint64_t off)
461 {
462 	nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl;
463 	uint32_t ndw;
464 
465 	if (!nvme_field_check_one(ctrl, off, "e6 dump", &nvme_wdc_e6_check_off,
466 	    0)) {
467 		return (false);
468 	}
469 
470 	ndw = off >> 2;
471 	if (!nvme_vuc_req_set_cdw13(req->wer_vuc, ndw)) {
472 		return (false);
473 	}
474 
475 	nvme_wdc_e6_req_clear_need(req, NVME_WDC_E6_REQ_FIELD_OFFSET);
476 	return (nvme_ctrl_success(ctrl));
477 }
478 
479 bool
480 nvme_wdc_e6_req_set_output(nvme_wdc_e6_req_t *req, void *buf, size_t len)
481 {
482 	nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl;
483 
484 	/*
485 	 * The set output validation handling takes care of all the actual
486 	 * normal field validation work that we need.
487 	 */
488 	if (!nvme_vuc_req_set_output(req->wer_vuc, buf, len)) {
489 		return (false);
490 	}
491 
492 	nvme_wdc_e6_req_clear_need(req, NVME_WDC_E6_REQ_FIELD_LEN);
493 	return (nvme_ctrl_success(ctrl));
494 }
495 
496 bool
497 nvme_wdc_e6_req_exec(nvme_wdc_e6_req_t *req)
498 {
499 	nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl;
500 
501 	if (req->wer_need != 0) {
502 		return (nvme_field_miss_err(ctrl, nvme_wdc_e6_req_fields,
503 		    ARRAY_SIZE(nvme_wdc_e6_req_fields),
504 		    NVME_ERR_WDC_E6_REQ_MISSING_FIELDS, "wdc e6",
505 		    req->wer_need));
506 	}
507 
508 	if (!nvme_vuc_req_exec(req->wer_vuc)) {
509 		return (false);
510 	}
511 
512 	return (nvme_ctrl_success(ctrl));
513 }
514 
515 static bool
516 nvme_wdc_assert_common(nvme_ctrl_t *ctrl, uint32_t subcmd)
517 {
518 	nvme_vuc_req_t *req = NULL;
519 	const char *name = subcmd == WDC_VUC_ASSERT_SUB_CLEAR ?
520 	    "wdc/clear-assert" : "wdc/inject-assert";
521 	uint32_t cdw12 = WDC_VUC_ASSERT_CMD | (subcmd << 8);
522 
523 	if (!nvme_vendor_vuc_supported(ctrl, name)) {
524 		return (false);
525 	}
526 
527 	if (!nvme_vuc_req_init(ctrl, &req)) {
528 		return (false);
529 	}
530 
531 	if (!nvme_vuc_req_set_opcode(req, WDC_VUC_ASSERT_OPC) ||
532 	    !nvme_vuc_req_set_cdw12(req, cdw12) ||
533 	    !nvme_vuc_req_set_timeout(req, nvme_wdc_assert_timeout) ||
534 	    !nvme_vuc_req_exec(req)) {
535 		nvme_vuc_req_fini(req);
536 		return (false);
537 	}
538 
539 	nvme_vuc_req_fini(req);
540 	return (nvme_ctrl_success(ctrl));
541 }
542 
543 bool
544 nvme_wdc_assert_clear(nvme_ctrl_t *ctrl)
545 {
546 	return (nvme_wdc_assert_common(ctrl, WDC_VUC_ASSERT_SUB_CLEAR));
547 }
548 
549 bool
550 nvme_wdc_assert_inject(nvme_ctrl_t *ctrl)
551 {
552 	return (nvme_wdc_assert_common(ctrl, WDC_VUC_ASSERT_SUB_INJECT));
553 }
554