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