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_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 nvme_vuc_req_t * 345 nvme_wdc_resize_vuc(nvme_ctrl_t *ctrl, uint8_t subcmd, uint32_t gib) 346 { 347 nvme_vuc_req_t *req = NULL; 348 uint32_t cdw12 = WDC_VUC_RESIZE_CMD | ((uint32_t)subcmd << 8); 349 350 if (!nvme_vendor_vuc_supported(ctrl, "wdc/resize")) { 351 return (false); 352 } 353 354 if (!nvme_vuc_req_init(ctrl, &req)) { 355 return (false); 356 } 357 358 if (!nvme_vuc_req_set_opcode(req, WDC_VUC_RESIZE_OPC) || 359 !nvme_vuc_req_set_cdw12(req, cdw12) || 360 !nvme_vuc_req_set_cdw13(req, gib) || 361 !nvme_vuc_req_set_timeout(req, nvme_wdc_resize_timeout)) { 362 nvme_vuc_req_fini(req); 363 return (false); 364 } 365 366 return (req); 367 } 368 369 bool 370 nvme_wdc_resize_get(nvme_ctrl_t *ctrl, uint32_t *gbp) 371 { 372 nvme_vuc_req_t *vuc; 373 374 if (gbp == NULL) { 375 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0, 376 "encountered invalid uint32_t pointer: %p", gbp)); 377 } 378 379 if ((vuc = nvme_wdc_resize_vuc(ctrl, WDC_VUC_RESIZE_SUB_GET, 0)) == 380 NULL) { 381 return (false); 382 } 383 384 if (!nvme_vuc_req_exec(vuc)) { 385 nvme_vuc_req_fini(vuc); 386 return (false); 387 } 388 389 if (!nvme_vuc_req_get_cdw0(vuc, gbp)) { 390 nvme_vuc_req_fini(vuc); 391 return (false); 392 } 393 394 return (nvme_ctrl_success(ctrl)); 395 } 396 397 bool 398 nvme_wdc_resize_set(nvme_ctrl_t *ctrl, uint32_t gb) 399 { 400 nvme_vuc_req_t *vuc; 401 402 if ((vuc = nvme_wdc_resize_vuc(ctrl, WDC_VUC_RESIZE_SUB_SET, gb)) == 403 NULL) { 404 return (false); 405 } 406 407 if (!nvme_vuc_req_set_impact(vuc, NVME_VUC_DISC_IMPACT_DATA | 408 NVME_VUC_DISC_IMPACT_NS)) { 409 nvme_vuc_req_fini(vuc); 410 return (false); 411 } 412 413 if (!nvme_vuc_req_exec(vuc)) { 414 nvme_vuc_req_fini(vuc); 415 return (false); 416 } 417 418 nvme_vuc_req_fini(vuc); 419 return (nvme_ctrl_success(ctrl)); 420 } 421 422 void 423 nvme_wdc_e6_req_fini(nvme_wdc_e6_req_t *req) 424 { 425 if (req == NULL) { 426 return; 427 } 428 429 nvme_vuc_req_fini(req->wer_vuc); 430 req->wer_vuc = NULL; 431 free(req); 432 } 433 434 bool 435 nvme_wdc_e6_req_init(nvme_ctrl_t *ctrl, nvme_wdc_e6_req_t **reqp) 436 { 437 nvme_wdc_e6_req_t *req; 438 439 if (reqp == NULL) { 440 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0, 441 "encountered invalid nvme_commit_req_t output pointer: %p", 442 reqp)); 443 } 444 445 if (!nvme_vendor_vuc_supported(ctrl, "wdc/e6dump")) { 446 return (false); 447 } 448 449 req = calloc(1, sizeof (nvme_wdc_e6_req_t)); 450 if (req == NULL) { 451 int e = errno; 452 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to " 453 "allocate memory for a new nvme_wdc_e6_req_t: %s", 454 strerror(e))); 455 } 456 457 if (!nvme_vuc_req_init(ctrl, &req->wer_vuc)) { 458 nvme_wdc_e6_req_fini(req); 459 return (false); 460 } 461 462 /* 463 * The documentation suggests we must explicitly set the mode in cdw12 464 * to zero. While that should be the default, we do anyways. 465 */ 466 if (!nvme_vuc_req_set_opcode(req->wer_vuc, WDC_VUC_E6_DUMP_OPC) || 467 !nvme_vuc_req_set_cdw12(req->wer_vuc, 0) || 468 !nvme_vuc_req_set_timeout(req->wer_vuc, nvme_wdc_e6_timeout)) { 469 nvme_wdc_e6_req_fini(req); 470 return (false); 471 } 472 473 for (size_t i = 0; i < ARRAY_SIZE(nvme_wdc_e6_req_fields); i++) { 474 if (nvme_wdc_e6_req_fields[i].nlfi_def_req) { 475 req->wer_need |= 1 << i; 476 } 477 } 478 479 *reqp = req; 480 return (nvme_ctrl_success(ctrl)); 481 } 482 483 static void 484 nvme_wdc_e6_req_set_need(nvme_wdc_e6_req_t *req, 485 nvme_wdc_e6_req_field_t field) 486 { 487 req->wer_need |= 1 << field; 488 } 489 490 static void 491 nvme_wdc_e6_req_clear_need(nvme_wdc_e6_req_t *req, 492 nvme_wdc_e6_req_field_t field) 493 { 494 req->wer_need &= ~(1 << field); 495 } 496 497 static const nvme_field_check_t nvme_wdc_e6_check_off = { 498 nvme_wdc_e6_req_fields, NVME_WDC_E6_REQ_FIELD_OFFSET, 499 NVME_ERR_WDC_E6_OFFSET_RANGE, 0, 0 500 }; 501 502 bool 503 nvme_wdc_e6_req_set_offset(nvme_wdc_e6_req_t *req, uint64_t off) 504 { 505 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl; 506 uint32_t ndw; 507 508 if (!nvme_field_check_one(ctrl, off, "e6 dump", &nvme_wdc_e6_check_off, 509 0)) { 510 return (false); 511 } 512 513 ndw = off >> 2; 514 if (!nvme_vuc_req_set_cdw13(req->wer_vuc, ndw)) { 515 return (false); 516 } 517 518 nvme_wdc_e6_req_clear_need(req, NVME_WDC_E6_REQ_FIELD_OFFSET); 519 return (nvme_ctrl_success(ctrl)); 520 } 521 522 bool 523 nvme_wdc_e6_req_set_output(nvme_wdc_e6_req_t *req, void *buf, size_t len) 524 { 525 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl; 526 527 /* 528 * The set output validation handling takes care of all the actual 529 * normal field validation work that we need. 530 */ 531 if (!nvme_vuc_req_set_output(req->wer_vuc, buf, len)) { 532 return (false); 533 } 534 535 nvme_wdc_e6_req_clear_need(req, NVME_WDC_E6_REQ_FIELD_LEN); 536 return (nvme_ctrl_success(ctrl)); 537 } 538 539 bool 540 nvme_wdc_e6_req_clear_output(nvme_wdc_e6_req_t *req) 541 { 542 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl; 543 544 if (!nvme_vuc_req_clear_output(req->wer_vuc)) { 545 return (false); 546 } 547 548 nvme_wdc_e6_req_set_need(req, NVME_WDC_E6_REQ_FIELD_LEN); 549 return (nvme_ctrl_success(ctrl)); 550 } 551 552 bool 553 nvme_wdc_e6_req_exec(nvme_wdc_e6_req_t *req) 554 { 555 nvme_ctrl_t *ctrl = req->wer_vuc->nvr_ctrl; 556 557 if (req->wer_need != 0) { 558 return (nvme_field_miss_err(ctrl, nvme_wdc_e6_req_fields, 559 ARRAY_SIZE(nvme_wdc_e6_req_fields), 560 NVME_ERR_WDC_E6_REQ_MISSING_FIELDS, "wdc e6", 561 req->wer_need)); 562 } 563 564 if (!nvme_vuc_req_exec(req->wer_vuc)) { 565 return (false); 566 } 567 568 return (nvme_ctrl_success(ctrl)); 569 } 570 571 static bool 572 nvme_wdc_assert_common(nvme_ctrl_t *ctrl, uint32_t subcmd) 573 { 574 nvme_vuc_req_t *req = NULL; 575 const char *name = subcmd == WDC_VUC_ASSERT_SUB_CLEAR ? 576 "wdc/clear-assert" : "wdc/inject-assert"; 577 uint32_t cdw12 = WDC_VUC_ASSERT_CMD | (subcmd << 8); 578 579 if (!nvme_vendor_vuc_supported(ctrl, name)) { 580 return (false); 581 } 582 583 if (!nvme_vuc_req_init(ctrl, &req)) { 584 return (false); 585 } 586 587 if (!nvme_vuc_req_set_opcode(req, WDC_VUC_ASSERT_OPC) || 588 !nvme_vuc_req_set_cdw12(req, cdw12) || 589 !nvme_vuc_req_set_timeout(req, nvme_wdc_assert_timeout) || 590 !nvme_vuc_req_exec(req)) { 591 nvme_vuc_req_fini(req); 592 return (false); 593 } 594 595 nvme_vuc_req_fini(req); 596 return (nvme_ctrl_success(ctrl)); 597 } 598 599 bool 600 nvme_wdc_assert_clear(nvme_ctrl_t *ctrl) 601 { 602 return (nvme_wdc_assert_common(ctrl, WDC_VUC_ASSERT_SUB_CLEAR)); 603 } 604 605 bool 606 nvme_wdc_assert_inject(nvme_ctrl_t *ctrl) 607 { 608 return (nvme_wdc_assert_common(ctrl, WDC_VUC_ASSERT_SUB_INJECT)); 609 } 610