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 2025 Oxide Computer Company 14 */ 15 16 /* 17 * This file deals with validating and issuing various log page requests to an 18 * NVMe target. This contains information about all spec-based log pages. The 19 * get log page command has added a number of fields that have evolved over 20 * time. We validate that we're only sending commands to a device that we expect 21 * it to have a chance of understanding. In general, we only allow through 22 * unknown log pages that correspond to vendor-specific commands. 23 * 24 * We have two different tables of information that we use to drive and validate 25 * things here: 26 * 27 * 1) We have a list of fields that exist which include minimum controller 28 * versions and related functionality validation routines that operate off of 29 * the nvme_t. While tihs list includes things like the CSI and LID, these are 30 * things that may only be specified when we have a non-standard log page. 31 * 32 * 2) We then have a table of log pages that are supported which list which 33 * fields we allow for the device. Not all of this can be static. 34 * 35 * This file has been designed to be shareable between both userland and the 36 * kernel since the logic that libnvme wants to use is quite similar. 37 */ 38 39 #include "nvme_common.h" 40 41 #include <sys/sysmacros.h> 42 #ifdef _KERNEL 43 #include <sys/sunddi.h> 44 #include <sys/stdint.h> 45 #else 46 #include <stdio.h> 47 #include <inttypes.h> 48 #include <strings.h> 49 #endif 50 51 static bool 52 nvme_log_field_valid_lsp(const nvme_field_info_t *field, 53 const nvme_valid_ctrl_data_t *data, uint64_t lsp, char *msg, size_t msglen) 54 { 55 uint64_t max; 56 57 if (nvme_field_atleast(data, &nvme_vers_2v0)) { 58 max = NVME_LOG_MAX_LSP_2v0; 59 } else { 60 max = NVME_LOG_MAX_LSP; 61 } 62 63 return (nvme_field_range_check(field, 0, max, msg, msglen, lsp)); 64 } 65 66 static bool 67 nvme_log_field_supported_offset(const nvme_field_info_t *field, 68 const nvme_valid_ctrl_data_t *data, char *msg, size_t msglen) 69 { 70 if (data->vcd_id->id_lpa.lp_extsup != 0) { 71 return (true); 72 } 73 74 (void) snprintf(msg, msglen, "controller does not support field %s " 75 "(%s): missing extended data support in Log Page Attributes (LPA)", 76 field->nlfi_human, field->nlfi_spec); 77 return (false); 78 } 79 80 /* 81 * The offset is a full 64-bit byte value; however, it must be 4-byte aligned. 82 */ 83 static bool 84 nvme_log_field_valid_offset(const nvme_field_info_t *field, 85 const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen) 86 { 87 if ((size % NVME_DWORD_SIZE) != 0) { 88 (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is " 89 "invalid: value must be %u-byte aligned", field->nlfi_human, 90 field->nlfi_spec, size, NVME_DWORD_SIZE); 91 return (false); 92 } 93 94 return (true); 95 } 96 97 static bool 98 nvme_log_field_valid_size(const nvme_field_info_t *field, 99 const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen) 100 { 101 uint64_t max = NVME_LOG_MAX_SIZE; 102 103 if (nvme_field_atleast(data, &nvme_vers_1v2) && 104 data->vcd_id->id_lpa.lp_extsup != 0) { 105 max = NVME_LOG_MAX_SIZE_1v2; 106 } 107 108 /* 109 * The NVMe specification operates in terms of uint32_t (dword) units. 110 * Make sure that we are operating within that constraint. 111 */ 112 if ((size % 4) != 0) { 113 (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is " 114 "invalid: value must be 4-byte aligned", field->nlfi_human, 115 field->nlfi_spec, size); 116 return (false); 117 } 118 119 return (nvme_field_range_check(field, 4, max, msg, msglen, size)); 120 } 121 122 const nvme_field_info_t nvme_log_fields[] = { 123 [NVME_LOG_REQ_FIELD_LID] = { 124 .nlfi_vers = &nvme_vers_1v0, 125 .nlfi_max_size = NVME_LOG_MAX_LID, 126 .nlfi_spec = "lid", 127 .nlfi_human = "log ID", 128 .nlfi_def_req = true, 129 .nlfi_def_allow = true 130 }, 131 [NVME_LOG_REQ_FIELD_LSP] = { 132 .nlfi_vers = &nvme_vers_1v3, 133 .nlfi_valid = nvme_log_field_valid_lsp, 134 .nlfi_spec = "lsp", 135 .nlfi_human = "log specific field", 136 .nlfi_def_req = false, 137 .nlfi_def_allow = true 138 }, 139 [NVME_LOG_REQ_FIELD_LSI] = { 140 .nlfi_vers = &nvme_vers_1v4, 141 .nlfi_max_size = NVME_LOG_MAX_LSI, 142 .nlfi_spec = "lsi", 143 .nlfi_human = "log specific ID", 144 .nlfi_def_req = false, 145 .nlfi_def_allow = true 146 }, 147 [NVME_LOG_REQ_FIELD_SIZE] = { 148 .nlfi_vers = &nvme_vers_1v0, 149 .nlfi_valid = nvme_log_field_valid_size, 150 .nlfi_spec = "dptr/numd", 151 .nlfi_human = "output", 152 .nlfi_def_req = true, 153 .nlfi_def_allow = true 154 }, 155 [NVME_LOG_REQ_FIELD_CSI] = { 156 .nlfi_vers = &nvme_vers_2v0, 157 /* 158 * This has the field's maximum range right now, though NVMe 2.0 159 * only defines a few values. Because the kernel only allows 160 * through known log pages, we don't really bother to check the 161 * condensed range and allow vendor-specific logs to go wild. 162 */ 163 .nlfi_max_size = NVME_LOG_MAX_CSI, 164 .nlfi_spec = "csi", 165 .nlfi_human = "command set ID", 166 .nlfi_def_req = false, 167 .nlfi_def_allow = true 168 }, 169 [NVME_LOG_REQ_FIELD_RAE] = { 170 .nlfi_vers = &nvme_vers_1v3, 171 .nlfi_max_size = NVME_LOG_MAX_RAE, 172 .nlfi_spec = "rae", 173 .nlfi_human = "retain asynchronous event", 174 .nlfi_def_req = false, 175 .nlfi_def_allow = true 176 }, 177 [NVME_LOG_REQ_FIELD_OFFSET] = { 178 .nlfi_vers = &nvme_vers_1v2, 179 .nlfi_sup = nvme_log_field_supported_offset, 180 .nlfi_valid = nvme_log_field_valid_offset, 181 .nlfi_max_size = NVME_LOG_MAX_OFFSET, 182 .nlfi_spec = "lpo", 183 .nlfi_human = "log offset", 184 .nlfi_def_req = false, 185 .nlfi_def_allow = true 186 }, 187 [NVME_LOG_REQ_FIELD_NSID] = { 188 .nlfi_vers = &nvme_vers_1v0, 189 .nlfi_valid = nvme_field_valid_nsid, 190 .nlfi_spec = "nsid", 191 .nlfi_human = "namespace ID", 192 .nlfi_def_req = false, 193 .nlfi_def_allow = true 194 } 195 }; 196 197 const size_t nvme_log_nfields = ARRAY_SIZE(nvme_log_fields); 198 199 static uint64_t 200 nvme_lpd_error_len(const nvme_valid_ctrl_data_t *data, 201 const nvme_log_page_info_t *lpi) 202 { 203 const uint64_t nents = data->vcd_id->id_elpe + 1; 204 const uint64_t logsz = nents * sizeof (nvme_error_log_entry_t); 205 206 return (logsz); 207 } 208 209 static nvme_log_disc_scope_t 210 nvme_lpd_health_scope(const nvme_valid_ctrl_data_t *data, 211 const nvme_log_page_info_t *lpi) 212 { 213 nvme_log_disc_scope_t ret = NVME_LOG_SCOPE_CTRL; 214 215 if (nvme_field_atleast(data, &nvme_vers_1v0) && 216 data->vcd_id->id_lpa.lp_smart != 0) { 217 ret |= NVME_LOG_SCOPE_NS; 218 } 219 220 return (ret); 221 } 222 223 static bool 224 nvme_lpd_changens_sup(const nvme_valid_ctrl_data_t *data, 225 const nvme_log_page_info_t *lpi) 226 { 227 return (nvme_field_atleast(data, &nvme_vers_1v2) && 228 data->vcd_id->id_oaes.oaes_nsan != 0); 229 } 230 231 static bool 232 nvme_lpd_cmdeff_sup(const nvme_valid_ctrl_data_t *data, 233 const nvme_log_page_info_t *lpi) 234 { 235 return (nvme_field_atleast(data, &nvme_vers_1v2) && 236 data->vcd_id->id_lpa.lp_cmdeff != 0); 237 } 238 239 static bool 240 nvme_lpd_pev_sup(const nvme_valid_ctrl_data_t *data, 241 const nvme_log_page_info_t *lpi) 242 { 243 return (nvme_field_atleast(data, &nvme_vers_1v4) && 244 data->vcd_id->id_lpa.lp_persist != 0); 245 } 246 247 static bool 248 nvme_lpd_pev_len(uint64_t *outp, const void *data, size_t len) 249 { 250 nvme_pev_log_t pev; 251 252 if (len < sizeof (pev)) { 253 return (false); 254 } 255 256 (void) memcpy(&pev, data, sizeof (pev)); 257 *outp = pev.pel_tll; 258 return (true); 259 } 260 261 static bool 262 nvme_lpd_telemetry_sup(const nvme_valid_ctrl_data_t *data, 263 const nvme_log_page_info_t *lpi) 264 { 265 return (nvme_field_atleast(data, &nvme_vers_1v3) && 266 data->vcd_id->id_lpa.lp_telemetry != 0); 267 } 268 269 static bool 270 nvme_lpd_telemetry_len(uint64_t *outp, const void *data, size_t len) 271 { 272 nvme_telemetry_log_t telem; 273 uint64_t nblks; 274 275 if (len < sizeof (telem)) { 276 return (false); 277 } 278 279 (void) memcpy(&telem, data, sizeof (telem)); 280 281 /* 282 * See if we have section 4 information, then fall back to 3, 2, and 283 * finally 1 to determine the size. We then have to add the header. 284 */ 285 nblks = telem.ntl_thda4lb; 286 if (nblks == 0) 287 nblks = telem.ntl_thda3lb; 288 if (nblks == 0) 289 nblks = telem.ntl_thda2lb; 290 if (nblks == 0) 291 nblks = telem.ntl_thda1lb; 292 nblks++; 293 *outp = nblks * sizeof (telem); 294 return (true); 295 } 296 297 /* 298 * The short names here correspond to the well defined names in nvmeadm(8) and 299 * libnvme(3LIB) that users expect to be able to use. Please do not change them 300 * without accounting for aliases and backwards compatibility. The index of the 301 * supported log pages entry is expected to be the first entry here. This is 302 * relied upon by log discovery in nvme_log_discover_fetch_sup_logs(). 303 */ 304 const nvme_log_page_info_t nvme_std_log_pages[] = { { 305 .nlpi_short = "suplog", 306 .nlpi_human = "Supported Log Pages", 307 .nlpi_lid = NVME_LOGPAGE_SUP, 308 .nlpi_csi = NVME_CSI_NVM, 309 .nlpi_vers = &nvme_vers_2v0, 310 .nlpi_kind = NVME_LOG_ID_MANDATORY, 311 .nlpi_source = NVME_LOG_DISC_S_SPEC, 312 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 313 .nlpi_len = sizeof (nvme_suplog_log_t) 314 }, { 315 .nlpi_short = "error", 316 .nlpi_human = "Error information", 317 .nlpi_lid = NVME_LOGPAGE_ERROR, 318 .nlpi_csi = NVME_CSI_NVM, 319 .nlpi_vers = &nvme_vers_1v0, 320 .nlpi_kind = NVME_LOG_ID_MANDATORY, 321 .nlpi_source = NVME_LOG_DISC_S_SPEC, 322 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, 323 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 324 .nlpi_len_func = nvme_lpd_error_len 325 }, { 326 .nlpi_short = "health", 327 .nlpi_human = "SMART / Health information", 328 .nlpi_lid = NVME_LOGPAGE_HEALTH, 329 .nlpi_csi = NVME_CSI_NVM, 330 .nlpi_vers = &nvme_vers_1v0, 331 .nlpi_kind = NVME_LOG_ID_MANDATORY, 332 .nlpi_source = NVME_LOG_DISC_S_SPEC | NVME_LOG_DISC_S_ID_CTRL, 333 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, 334 .nlpi_scope_func = nvme_lpd_health_scope, 335 .nlpi_len = sizeof (nvme_health_log_t) 336 }, { 337 .nlpi_short = "firmware", 338 .nlpi_human = "Firmware Slot Information", 339 .nlpi_lid = NVME_LOGPAGE_FWSLOT, 340 .nlpi_csi = NVME_CSI_NVM, 341 .nlpi_vers = &nvme_vers_1v0, 342 .nlpi_kind = NVME_LOG_ID_MANDATORY, 343 .nlpi_source = NVME_LOG_DISC_S_SPEC, 344 .nlpi_disc = 0, 345 .nlpi_scope = NVME_LOG_SCOPE_NVM, 346 .nlpi_len = sizeof (nvme_fwslot_log_t), 347 }, { 348 .nlpi_short = "changens", 349 .nlpi_human = "changed namespaces", 350 .nlpi_lid = NVME_LOGPAGE_NSCHANGE, 351 .nlpi_csi = NVME_CSI_NVM, 352 .nlpi_vers = &nvme_vers_1v2, 353 .nlpi_sup_func = nvme_lpd_changens_sup, 354 .nlpi_kind = NVME_LOG_ID_OPTIONAL, 355 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, 356 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, 357 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 358 .nlpi_len = sizeof (nvme_nschange_list_t) 359 }, { 360 .nlpi_short = "cmdeff", 361 .nlpi_human = "commands supported and effects", 362 .nlpi_lid = NVME_LOGPAGE_CMDSUP, 363 .nlpi_csi = NVME_CSI_NVM, 364 .nlpi_vers = &nvme_vers_1v2, 365 .nlpi_sup_func = nvme_lpd_cmdeff_sup, 366 .nlpi_kind = NVME_LOG_ID_OPTIONAL, 367 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, 368 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 369 .nlpi_len = sizeof (nvme_cmdeff_log_t) 370 }, { 371 .nlpi_short = "pev", 372 .nlpi_human = "persistent event log", 373 .nlpi_lid = NVME_LOGPAGE_PEV, 374 .nlpi_csi = NVME_CSI_NVM, 375 .nlpi_vers = &nvme_vers_1v4, 376 .nlpi_sup_func = nvme_lpd_pev_sup, 377 .nlpi_kind = NVME_LOG_ID_OPTIONAL, 378 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, 379 .nlpi_disc = NVME_LOG_DISC_F_NEED_LSP, 380 .nlpi_scope = NVME_LOG_SCOPE_NVM, 381 .nlpi_len = sizeof (nvme_pev_log_t), 382 .nlpi_var_func = nvme_lpd_pev_len 383 }, { 384 .nlpi_short = "telemetry", 385 .nlpi_human = "telemetry host-initiated", 386 .nlpi_lid = NVME_LOGPAGE_TELMHOST, 387 .nlpi_csi = NVME_CSI_NVM, 388 .nlpi_vers = &nvme_vers_1v3, 389 .nlpi_sup_func = nvme_lpd_telemetry_sup, 390 .nlpi_kind = NVME_LOG_ID_OPTIONAL, 391 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, 392 .nlpi_disc = NVME_LOG_DISC_F_NEED_LSP, 393 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 394 .nlpi_len = sizeof (nvme_telemetry_log_t), 395 .nlpi_var_func = nvme_lpd_telemetry_len 396 } }; 397 398 const size_t nvme_std_log_npages = ARRAY_SIZE(nvme_std_log_pages); 399 400 nvme_log_disc_scope_t 401 nvme_log_page_info_scope(const nvme_log_page_info_t *info, 402 const nvme_valid_ctrl_data_t *data) 403 { 404 if (info->nlpi_scope_func != NULL) { 405 return (info->nlpi_scope_func(data, info)); 406 } else { 407 return (info->nlpi_scope); 408 } 409 } 410 411 uint64_t 412 nvme_log_page_info_size(const nvme_log_page_info_t *info, 413 const nvme_valid_ctrl_data_t *data, bool *var) 414 { 415 uint64_t len; 416 *var = info->nlpi_var_func != NULL; 417 418 if (info->nlpi_len_func != NULL) { 419 len = info->nlpi_len_func(data, info); 420 } else { 421 len = info->nlpi_len; 422 } 423 424 /* 425 * Some vendor-specific log pages are not documented to have 4-byte 426 * aligned lengths. This means that to get the full log page you must 427 * round this up to ensure that you end up with a valid request. We opt 428 * to do this here rather than have to check every single log page data 429 * structure and fix it up manually. While it means consumers that are 430 * using this to ignore information about the type itself may 431 * erroneously display extra bytes (e.g. nvmeadm's default hex dumper), 432 * that's better than getting an error or truncating the data. 433 */ 434 return (P2ROUNDUP(len, NVME_DWORD_SIZE)); 435 } 436 437 bool 438 nvme_log_page_info_supported(const nvme_log_page_info_t *info, 439 const nvme_valid_ctrl_data_t *data) 440 { 441 bool vers, sup_func; 442 443 if (info->nlpi_vers != NULL) { 444 vers = nvme_field_atleast(data, info->nlpi_vers); 445 } else { 446 vers = true; 447 } 448 449 if (info->nlpi_sup_func != NULL) { 450 sup_func = info->nlpi_sup_func(data, info); 451 } else { 452 sup_func = true; 453 } 454 455 return (vers && sup_func); 456 } 457