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