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 2026 Oxide Computer Company 14 */ 15 16 /* 17 * libnvme logic that covers the OCP Datacenter NVMe SSD specification. 18 */ 19 20 #include <string.h> 21 #include <sys/sysmacros.h> 22 #include <sys/nvme/ocp.h> 23 24 #include "libnvme_impl.h" 25 26 static bool 27 nvme_ocp_telstr_var_len(uint64_t *outp, const void *data, size_t len) 28 { 29 ocp_vul_telstr_t telstr; 30 size_t need = offsetof(ocp_vul_telstr_t, ots_rsvd40); 31 32 if (len < offsetof(ocp_vul_telstr_t, ots_rsvd40)) { 33 return (false); 34 } 35 36 (void) memcpy(&telstr, data, need); 37 if (telstr.ots_sls / sizeof (uint32_t) >= SIZE_MAX) { 38 return (false); 39 } 40 *outp = telstr.ots_sls * sizeof (uint32_t); 41 return (true); 42 } 43 44 static bool 45 nvme_ocp_hwcomp_var_len(uint64_t *outp, const void *data, size_t len) 46 { 47 ocp_vul_hw_comp_t comp; 48 uint32_t dlen; 49 uint64_t mult; 50 51 if (len < sizeof (ocp_vul_hw_comp_t)) { 52 return (false); 53 } 54 55 56 (void) memcpy(&comp, data, sizeof (ocp_vul_hw_comp_t)); 57 58 /* 59 * The hardware component log has a 16-byte number that is used to 60 * indicate the log page length. In version 1 of the log page this is a 61 * value of uint32_t's. In version 2 of the log page this is in bytes. 62 * Because of these changes we require a known version to know how to 63 * deal with these things. 64 * 65 * When we encounter a log page with more than 4 GiB of data in it, we 66 * can come back to this as we're going to need to improve the logic 67 * elsewhere to stream this rather than use a single buffer. 68 */ 69 switch (comp.ohc_vers) { 70 case 1: 71 mult = sizeof (uint32_t); 72 break; 73 case 2: 74 mult = 1; 75 break; 76 default: 77 return (false); 78 } 79 80 for (size_t i = 4; i < 16; i++) { 81 if (comp.ohc_len[i] != 0) { 82 return (false); 83 } 84 } 85 86 (void) memcpy(&dlen, comp.ohc_len, sizeof (dlen)); 87 *outp = (uint64_t)dlen * mult; 88 if (*outp < sizeof (ocp_vul_hw_comp_t)) { 89 return (false); 90 } 91 92 return (true); 93 } 94 95 const nvme_log_page_info_t ocp_log_smart = { 96 .nlpi_short = "ocp/smart", 97 .nlpi_human = "OCP SMART / Health Information", 98 .nlpi_lid = OCP_LOG_DSSD_SMART, 99 .nlpi_csi = NVME_CSI_NVM, 100 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 101 .nlpi_source = NVME_LOG_DISC_S_DB, 102 .nlpi_scope = NVME_LOG_SCOPE_NVM, 103 .nlpi_len = sizeof (ocp_vul_smart_t), 104 }; 105 106 const nvme_log_page_info_t ocp_log_errrec = { 107 .nlpi_short = "ocp/errrec", 108 .nlpi_human = "OCP Error Recovery", 109 .nlpi_lid = OCP_LOG_DSSD_ERROR_REC, 110 .nlpi_csi = NVME_CSI_NVM, 111 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 112 .nlpi_source = NVME_LOG_DISC_S_DB, 113 .nlpi_scope = NVME_LOG_SCOPE_NVM, 114 .nlpi_len = sizeof (ocp_vul_errrec_t), 115 }; 116 117 const nvme_log_page_info_t ocp_log_fwact = { 118 .nlpi_short = "ocp/fwact", 119 .nlpi_human = "OCP Firmware Activation", 120 .nlpi_lid = OCP_LOG_DSSD_FWACT, 121 .nlpi_csi = NVME_CSI_NVM, 122 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 123 .nlpi_source = NVME_LOG_DISC_S_DB, 124 .nlpi_scope = NVME_LOG_SCOPE_NVM, 125 .nlpi_len = sizeof (ocp_vul_fwact_t), 126 }; 127 128 const nvme_log_page_info_t ocp_log_lat = { 129 .nlpi_short = "ocp/latency", 130 .nlpi_human = "OCP Latency Monitor", 131 .nlpi_lid = OCP_LOG_DSSD_LATENCY, 132 .nlpi_csi = NVME_CSI_NVM, 133 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 134 .nlpi_source = NVME_LOG_DISC_S_DB, 135 .nlpi_scope = NVME_LOG_SCOPE_NVM, 136 .nlpi_len = sizeof (ocp_vul_lat_t), 137 }; 138 139 const nvme_log_page_info_t ocp_log_devcap = { 140 .nlpi_short = "ocp/devcap", 141 .nlpi_human = "OCP Device Capabilities", 142 .nlpi_lid = OCP_LOG_DSSD_DEV_CAP, 143 .nlpi_csi = NVME_CSI_NVM, 144 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 145 .nlpi_source = NVME_LOG_DISC_S_DB, 146 .nlpi_scope = NVME_LOG_SCOPE_NVM, 147 .nlpi_len = sizeof (ocp_vul_devcap_t), 148 }; 149 150 const nvme_log_page_info_t ocp_log_unsup = { 151 .nlpi_short = "ocp/unsup", 152 .nlpi_human = "OCP Unsupported Requirements", 153 .nlpi_lid = OCP_LOG_DSSD_UNSUP_REQ, 154 .nlpi_csi = NVME_CSI_NVM, 155 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 156 .nlpi_source = NVME_LOG_DISC_S_DB, 157 .nlpi_scope = NVME_LOG_SCOPE_NVM, 158 .nlpi_len = sizeof (ocp_vul_unsup_req_t), 159 }; 160 161 const nvme_log_page_info_t ocp_log_hwcomp = { 162 .nlpi_short = "ocp/hwcomp", 163 .nlpi_human = "Hardware Component", 164 .nlpi_lid = OCP_LOG_DSSD_HW_COMP, 165 .nlpi_csi = NVME_CSI_NVM, 166 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 167 .nlpi_source = NVME_LOG_DISC_S_DB, 168 .nlpi_scope = NVME_LOG_SCOPE_NVM, 169 .nlpi_len = sizeof (ocp_vul_hw_comp_t), 170 .nlpi_var_func = nvme_ocp_hwcomp_var_len 171 }; 172 173 const nvme_log_page_info_t ocp_log_telstr = { 174 .nlpi_short = "ocp/telstr", 175 .nlpi_human = "OCP Telemetry String", 176 .nlpi_lid = OCP_LOG_DSSD_TELEMETRY, 177 .nlpi_csi = NVME_CSI_NVM, 178 .nlpi_kind = NVME_LOG_ID_VENDOR_SPECIFIC, 179 .nlpi_source = NVME_LOG_DISC_S_DB, 180 .nlpi_scope = NVME_LOG_SCOPE_NVM, 181 .nlpi_len = offsetof(ocp_vul_telstr_t, ots_rsvd40), 182 .nlpi_var_func = nvme_ocp_telstr_var_len 183 }; 184 185 const nvme_feat_info_t ocp_feat_errinj = { 186 .nfeat_short = "ocp/errinj", 187 .nfeat_spec = "OCP Error Injection", 188 .nfeat_fid = OCP_FEAT_DSSD_ERR_INJ, 189 .nfeat_kind = NVME_FEAT_VENDOR_SPECIFIC, 190 .nfeat_scope = NVME_FEAT_SCOPE_NVM, 191 .nfeat_csi = NVME_FEAT_CSI_NONE, 192 .nfeat_in_get = NVME_GET_FEAT_F_DATA, 193 .nfeat_in_set = NVME_SET_FEAT_F_CDW11 | NVME_SET_FEAT_F_DATA, 194 .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 | NVME_FEAT_OUTPUT_DATA, 195 /* 196 * The OCP specification reqiurement SERRI-9 says that this must always 197 * be a 4K payload that has [0, 127] entries. 198 */ 199 .nfeat_len = (OCP_ERRINJ_MAX_INJECT + 1) * sizeof (ocp_vuf_errinj_t) 200 }; 201 202 /* 203 * This was originally called the EOL/PLP Failure Mode, but the EOL was dropped 204 * starting in version 2.6 of the OCP DSSD NVMe specification. 205 */ 206 const nvme_feat_info_t ocp_feat_plpfail = { 207 .nfeat_short = "ocp/plpfail", 208 .nfeat_spec = "OCP PLP Failure Mode", 209 .nfeat_fid = OCP_FEAT_DSSD_EOLPLP, 210 .nfeat_kind = NVME_FEAT_VENDOR_SPECIFIC, 211 .nfeat_scope = NVME_FEAT_SCOPE_NVM, 212 .nfeat_csi = NVME_FEAT_CSI_NONE, 213 .nfeat_in_set = NVME_SET_FEAT_F_CDW11, 214 .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 215 }; 216 217 const nvme_feat_info_t ocp_feat_plphealth = { 218 .nfeat_short = "ocp/plphealth", 219 .nfeat_spec = "OCP PLP Health Check Interval", 220 .nfeat_fid = OCP_FEAT_DSSD_PLP_HEALTH, 221 .nfeat_kind = NVME_FEAT_VENDOR_SPECIFIC, 222 .nfeat_scope = NVME_FEAT_SCOPE_NVM, 223 .nfeat_csi = NVME_FEAT_CSI_NONE, 224 .nfeat_in_set = NVME_SET_FEAT_F_CDW11, 225 .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 226 }; 227 228 typedef enum { 229 NVME_OCP_ERRINJ_FIELD_TYPE = 0, 230 NVME_OCP_ERRINJ_FIELD_NRTDE, 231 NVME_OCP_ERRINJ_FIELD_LD 232 } nvme_ocp_errinj_req_field_t; 233 234 const nvme_field_info_t nvme_ocp_errinj_fields[] = { 235 [NVME_OCP_ERRINJ_FIELD_TYPE] = { 236 .nlfi_vers = &nvme_vers_1v0, 237 .nlfi_max_size = UINT16_MAX, 238 .nlfi_spec = "eit", 239 .nlfi_human = "error injection type", 240 .nlfi_def_req = true, 241 .nlfi_def_allow = true 242 }, 243 [NVME_OCP_ERRINJ_FIELD_NRTDE] = { 244 .nlfi_vers = &nvme_vers_1v0, 245 .nlfi_max_size = UINT16_MAX, 246 .nlfi_spec = "nrtde", 247 .nlfi_human = "number of reads to trigger device panic", 248 .nlfi_def_req = false, 249 .nlfi_def_allow = true 250 }, 251 /* 252 * This field was added in OCP DSSD v2.7. It only applies to event 0xe. 253 * While we could get the DSSD spec version out of the health and error 254 * reporting to see if this is supported as part of a version check, for 255 * now we just let it be set and allow the device to allow or deny it. 256 */ 257 [NVME_OCP_ERRINJ_FIELD_LD] = { 258 .nlfi_vers = &nvme_vers_1v0, 259 .nlfi_max_size = UINT32_MAX, 260 .nlfi_spec = "ld", 261 .nlfi_human = "latency duration", 262 .nlfi_def_req = false, 263 .nlfi_def_allow = true 264 } 265 }; 266 267 void 268 nvme_ocp_errinj_req_fini(nvme_ocp_errinj_req_t *req) 269 { 270 if (req != NULL) { 271 nvme_set_feat_req_fini(req->oer_feat); 272 free(req); 273 } 274 } 275 276 bool 277 nvme_ocp_errinj_req_init(nvme_ctrl_t *ctrl, nvme_ocp_errinj_req_t **reqp) 278 { 279 nvme_ocp_errinj_req_t *req; 280 281 if (reqp == NULL) { 282 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0, 283 "encountered invalid nvme_ocp_errinj_req_t output pointer: " 284 "%p", reqp)); 285 } 286 287 req = calloc(1, sizeof (nvme_ocp_errinj_req_t)); 288 if (req == NULL) { 289 int e = errno; 290 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to " 291 "allocate memory for a new nvm_ocp_errinj_req_t: %s", 292 strerror(e))); 293 } 294 295 if (!nvme_vendor_feature_supported(ctrl, "ocp/errinj") || 296 !nvme_set_feat_req_init_by_name(ctrl, "ocp/errinj", 0, NULL, 297 &req->oer_feat)) { 298 nvme_ocp_errinj_req_fini(req); 299 return (false); 300 } 301 302 for (size_t i = 0; i < ARRAY_SIZE(nvme_ocp_errinj_fields); i++) { 303 if (nvme_ocp_errinj_fields[i].nlfi_def_req) { 304 req->oer_need |= 1 << i; 305 } 306 307 if (nvme_ocp_errinj_fields[i].nlfi_def_allow) { 308 req->oer_allow |= 1 << i; 309 } 310 } 311 312 *reqp = req; 313 return (nvme_ctrl_success(ctrl)); 314 } 315 316 static void 317 nvme_ocp_errinj_req_clear_need(nvme_ocp_errinj_req_t *req, 318 nvme_ocp_errinj_req_field_t field) 319 { 320 req->oer_need &= ~(1 << field); 321 } 322 323 static const nvme_field_check_t nvme_ocp_errinj_check_type = { 324 nvme_ocp_errinj_fields, NVME_OCP_ERRINJ_FIELD_TYPE, 325 NVME_ERR_OCP_ERRINJ_TYPE_RANGE, 0, 0 326 }; 327 328 bool 329 nvme_ocp_errinj_req_set_type(nvme_ocp_errinj_req_t *req, uint32_t type) 330 { 331 nvme_ctrl_t *ctrl = req->oer_feat->sfr_ctrl; 332 333 if (!nvme_field_check_one(ctrl, type, "ocp error injection", 334 &nvme_ocp_errinj_check_type, req->oer_allow)) { 335 return (false); 336 } 337 338 req->oer_err[0].oei_type = (uint16_t)type; 339 nvme_ocp_errinj_req_clear_need(req, NVME_OCP_ERRINJ_FIELD_TYPE); 340 return (nvme_ctrl_success(ctrl)); 341 } 342 343 static const nvme_field_check_t nvme_ocp_errinj_check_nread = { 344 nvme_ocp_errinj_fields, NVME_OCP_ERRINJ_FIELD_NRTDE, 345 NVME_ERR_OCP_ERRINJ_NRTDE_RANGE, 0, 0 346 }; 347 348 bool 349 nvme_ocp_errinj_req_set_nrtde(nvme_ocp_errinj_req_t *req, uint32_t nreads) 350 { 351 nvme_ctrl_t *ctrl = req->oer_feat->sfr_ctrl; 352 353 if (!nvme_field_check_one(ctrl, nreads, "ocp error injection", 354 &nvme_ocp_errinj_check_nread, req->oer_allow)) { 355 return (false); 356 } 357 358 req->oer_err[0].oei_nrtde = (uint16_t)nreads; 359 nvme_ocp_errinj_req_clear_need(req, NVME_OCP_ERRINJ_FIELD_NRTDE); 360 return (nvme_ctrl_success(ctrl)); 361 } 362 363 static const nvme_field_check_t nvme_ocp_errinj_check_ld = { 364 nvme_ocp_errinj_fields, NVME_OCP_ERRINJ_FIELD_LD, 365 NVME_ERR_OCP_ERRINJ_LD_RANGE, NVME_ERR_OCP_ERRINJ_LD_UNSUP, 366 NVME_ERR_OCP_ERRINJ_LD_UNUSE 367 }; 368 369 /* 370 * We can't check if this field can be set until after an event is set. We defer 371 * checking it right now until exec. We don't have a copy of the OCP 372 * SMART/Health log which means we can't right now send the unsupported error 373 * check. The error constant exists so we can figure out a means of doing so in 374 * the future if that makes sense. 375 */ 376 bool 377 nvme_ocp_errinj_req_set_ld(nvme_ocp_errinj_req_t *req, uint32_t lat) 378 { 379 nvme_ctrl_t *ctrl = req->oer_feat->sfr_ctrl; 380 381 if (!nvme_field_check_one(ctrl, lat, "ocp error injection", 382 &nvme_ocp_errinj_check_ld, req->oer_allow)) { 383 return (false); 384 } 385 386 req->oer_err[0].oei_lat = lat; 387 nvme_ocp_errinj_req_clear_need(req, NVME_OCP_ERRINJ_FIELD_LD); 388 return (nvme_ctrl_success(ctrl)); 389 } 390 391 bool 392 nvme_ocp_errinj_req_exec(nvme_ocp_errinj_req_t *req) 393 { 394 nvme_ctrl_t *ctrl = req->oer_feat->sfr_ctrl; 395 396 if (req->oer_need != 0) { 397 return (nvme_field_miss_err(ctrl, nvme_ocp_errinj_fields, 398 ARRAY_SIZE(nvme_ocp_errinj_fields), 399 NVME_ERR_OCP_ERRINJ_REQ_MISSING_FIELDS, 400 "ocp error injection", req->oer_need)); 401 } 402 403 /* 404 * Now that we know the tpye of request, check that the type makes sense 405 * and evaluate if the latency duration makes sense. 406 */ 407 if (req->oer_err[0].oei_lat != 0 && req->oer_err[0].oei_type != 408 OCP_ERRINJ_T_LATENCY_SPIKE) { 409 return (nvme_ctrl_error(ctrl, NVME_ERR_OCP_ERRINJ_LD_UNUSE, 0, 410 "the latency duration field may only be used with the " 411 "latency spike event type (0x%x): found 0x%x", 412 OCP_ERRINJ_T_LATENCY_SPIKE, req->oer_err[0].oei_type)); 413 } 414 415 /* 416 * Beginning in OCP DSSD version 2.7 injecting a single event and 417 * ensuring only a single instance was present became required. This is 418 * likely a constraint of older devices which is why we only allow a 419 * single event to be injected and always set that. 420 */ 421 req->oer_err[0].oei_flags = OCP_ERRINJ_F_ENABLE | OCP_ERRINJ_F_SINGLE; 422 if (!nvme_set_feat_req_set_cdw11(req->oer_feat, 1) || 423 !nvme_set_feat_req_set_input(req->oer_feat, req->oer_err, 424 sizeof (req->oer_err))) { 425 return (false); 426 } 427 428 return (nvme_set_feat_req_exec(req->oer_feat)); 429 } 430 431 /* 432 * Send a request to clear all pending errors. 433 */ 434 bool 435 nvme_ocp_errinj_clear(nvme_ctrl_t *ctrl) 436 { 437 nvme_set_feat_req_t *req; 438 bool ret = false; 439 ocp_vuf_errinj_t table[OCP_ERRINJ_MAX_INJECT + 1]; 440 441 if (!nvme_vendor_feature_supported(ctrl, "ocp/errinj") || 442 !nvme_set_feat_req_init_by_name(ctrl, "ocp/errinj", 0, NULL, 443 &req)) { 444 return (false); 445 } 446 447 /* 448 * The specification isn't quite clear if no table should be sent if the 449 * event count is zero, so we always send this regardless. A zeroed 450 * table suggests no entries are valid and coincides with the cdw11 451 * value. 452 */ 453 (void) memset(table, 0, sizeof (table)); 454 if (nvme_set_feat_req_set_cdw11(req, 0) && 455 nvme_set_feat_req_set_input(req, table, sizeof (table)) && 456 nvme_set_feat_req_exec(req)) { 457 ret = nvme_ctrl_success(ctrl); 458 } 459 460 nvme_set_feat_req_fini(req); 461 return (ret); 462 } 463