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 * 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 #endif 49 50 static bool 51 nvme_log_field_valid_lsp(const nvme_field_info_t *field, 52 const nvme_valid_ctrl_data_t *data, uint64_t lsp, char *msg, size_t msglen) 53 { 54 uint64_t max; 55 56 if (nvme_field_atleast(data, &nvme_vers_2v0)) { 57 max = NVME_LOG_MAX_LSP_2v0; 58 } else { 59 max = NVME_LOG_MAX_LSP; 60 } 61 62 return (nvme_field_range_check(field, 0, max, msg, msglen, lsp)); 63 } 64 65 static bool 66 nvme_log_field_supported_offset(const nvme_field_info_t *field, 67 const nvme_valid_ctrl_data_t *data, char *msg, size_t msglen) 68 { 69 if (data->vcd_id->id_lpa.lp_extsup != 0) { 70 return (true); 71 } 72 73 (void) snprintf(msg, msglen, "controller does not support field %s " 74 "(%s): missing extended data support in Log Page Attributes (LPA)", 75 field->nlfi_human, field->nlfi_spec); 76 return (false); 77 } 78 79 /* 80 * The offset is a full 64-bit byte value; however, it must be 4-byte aligned. 81 */ 82 static bool 83 nvme_log_field_valid_offset(const nvme_field_info_t *field, 84 const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen) 85 { 86 if ((size % NVME_DWORD_SIZE) != 0) { 87 (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is " 88 "invalid: value must be %u-byte aligned", field->nlfi_human, 89 field->nlfi_spec, size, NVME_DWORD_SIZE); 90 return (false); 91 } 92 93 return (true); 94 } 95 96 static bool 97 nvme_log_field_valid_size(const nvme_field_info_t *field, 98 const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen) 99 { 100 uint64_t max = NVME_LOG_MAX_SIZE; 101 102 if (nvme_field_atleast(data, &nvme_vers_1v2) && 103 data->vcd_id->id_lpa.lp_extsup != 0) { 104 max = NVME_LOG_MAX_SIZE_1v2; 105 } 106 107 /* 108 * The NVMe specification operates in terms of uint32_t (dword) units. 109 * Make sure that we are operating within that constraint. 110 */ 111 if ((size % 4) != 0) { 112 (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is " 113 "invalid: value must be 4-byte aligned", field->nlfi_human, 114 field->nlfi_spec, size); 115 return (false); 116 } 117 118 return (nvme_field_range_check(field, 4, max, msg, msglen, size)); 119 } 120 121 const nvme_field_info_t nvme_log_fields[] = { 122 [NVME_LOG_REQ_FIELD_LID] = { 123 .nlfi_vers = &nvme_vers_1v0, 124 .nlfi_max_size = NVME_LOG_MAX_LID, 125 .nlfi_spec = "lid", 126 .nlfi_human = "log ID", 127 .nlfi_def_req = true, 128 .nlfi_def_allow = true 129 }, 130 [NVME_LOG_REQ_FIELD_LSP] = { 131 .nlfi_vers = &nvme_vers_1v3, 132 .nlfi_valid = nvme_log_field_valid_lsp, 133 .nlfi_spec = "lsp", 134 .nlfi_human = "log specific field", 135 .nlfi_def_req = false, 136 .nlfi_def_allow = true 137 }, 138 [NVME_LOG_REQ_FIELD_LSI] = { 139 .nlfi_vers = &nvme_vers_1v4, 140 .nlfi_max_size = NVME_LOG_MAX_LSI, 141 .nlfi_spec = "lsi", 142 .nlfi_human = "log specific ID", 143 .nlfi_def_req = false, 144 .nlfi_def_allow = true 145 }, 146 [NVME_LOG_REQ_FIELD_SIZE] = { 147 .nlfi_vers = &nvme_vers_1v0, 148 .nlfi_valid = nvme_log_field_valid_size, 149 .nlfi_spec = "dptr/numd", 150 .nlfi_human = "output", 151 .nlfi_def_req = true, 152 .nlfi_def_allow = true 153 }, 154 [NVME_LOG_REQ_FIELD_CSI] = { 155 .nlfi_vers = &nvme_vers_2v0, 156 /* 157 * This has the field's maximum range right now, though NVMe 2.0 158 * only defines a few values. Because the kernel only allows 159 * through known log pages, we don't really bother to check the 160 * condensed range and allow vendor-specific logs to go wild. 161 */ 162 .nlfi_max_size = NVME_LOG_MAX_CSI, 163 .nlfi_spec = "csi", 164 .nlfi_human = "command set ID", 165 .nlfi_def_req = false, 166 .nlfi_def_allow = true 167 }, 168 [NVME_LOG_REQ_FIELD_RAE] = { 169 .nlfi_vers = &nvme_vers_1v3, 170 .nlfi_max_size = NVME_LOG_MAX_RAE, 171 .nlfi_spec = "rae", 172 .nlfi_human = "retain asynchronous event", 173 .nlfi_def_req = false, 174 .nlfi_def_allow = true 175 }, 176 [NVME_LOG_REQ_FIELD_OFFSET] = { 177 .nlfi_vers = &nvme_vers_1v2, 178 .nlfi_sup = nvme_log_field_supported_offset, 179 .nlfi_valid = nvme_log_field_valid_offset, 180 .nlfi_max_size = NVME_LOG_MAX_OFFSET, 181 .nlfi_spec = "lpo", 182 .nlfi_human = "log offset", 183 .nlfi_def_req = false, 184 .nlfi_def_allow = true 185 }, 186 [NVME_LOG_REQ_FIELD_NSID] = { 187 .nlfi_vers = &nvme_vers_1v0, 188 .nlfi_valid = nvme_field_valid_nsid, 189 .nlfi_spec = "nsid", 190 .nlfi_human = "namespace ID", 191 .nlfi_def_req = false, 192 .nlfi_def_allow = true 193 } 194 }; 195 196 size_t nvme_log_nfields = ARRAY_SIZE(nvme_log_fields); 197 198 static uint64_t 199 nvme_lpd_error_len(const nvme_valid_ctrl_data_t *data, 200 const nvme_log_page_info_t *lpi) 201 { 202 const uint64_t nents = data->vcd_id->id_elpe + 1; 203 const uint64_t logsz = nents * sizeof (nvme_error_log_entry_t); 204 205 return (logsz); 206 } 207 208 static nvme_log_disc_scope_t 209 nvme_lpd_health_scope(const nvme_valid_ctrl_data_t *data, 210 const nvme_log_page_info_t *lpi) 211 { 212 nvme_log_disc_scope_t ret = NVME_LOG_SCOPE_CTRL; 213 214 if (nvme_field_atleast(data, &nvme_vers_1v0) && 215 data->vcd_id->id_lpa.lp_smart != 0) { 216 ret |= NVME_LOG_SCOPE_NS; 217 } 218 219 return (ret); 220 } 221 222 static bool 223 nvme_lpd_changens_sup(const nvme_valid_ctrl_data_t *data, 224 const nvme_log_page_info_t *lpi) 225 { 226 return (nvme_field_atleast(data, &nvme_vers_1v2) && 227 data->vcd_id->id_oaes.oaes_nsan != 0); 228 } 229 230 static bool 231 nvme_lpd_cmdeff_sup(const nvme_valid_ctrl_data_t *data, 232 const nvme_log_page_info_t *lpi) 233 { 234 return (nvme_field_atleast(data, &nvme_vers_1v2) && 235 data->vcd_id->id_lpa.lp_cmdeff != 0); 236 } 237 238 /* 239 * The short names here correspond to the well defined names in nvmeadm(8) and 240 * libnvme(3LIB) that users expect to be able to use. Please do not change them 241 * without accounting for aliases and backwards compatibility. 242 */ 243 const nvme_log_page_info_t nvme_std_log_pages[] = { { 244 .nlpi_short = "suplog", 245 .nlpi_human = "Supported Log Pages", 246 .nlpi_lid = NVME_LOGPAGE_SUP, 247 .nlpi_csi = NVME_CSI_NVM, 248 .nlpi_vers = &nvme_vers_2v0, 249 .nlpi_kind = NVME_LOG_ID_MANDATORY, 250 .nlpi_source = NVME_LOG_DISC_S_SPEC, 251 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 252 .nlpi_len = sizeof (nvme_suplog_log_t) 253 }, { 254 .nlpi_short = "error", 255 .nlpi_human = "Error information", 256 .nlpi_lid = NVME_LOGPAGE_ERROR, 257 .nlpi_csi = NVME_CSI_NVM, 258 .nlpi_vers = &nvme_vers_1v0, 259 .nlpi_kind = NVME_LOG_ID_MANDATORY, 260 .nlpi_source = NVME_LOG_DISC_S_SPEC, 261 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, 262 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 263 .nlpi_len_func = nvme_lpd_error_len 264 }, { 265 .nlpi_short = "health", 266 .nlpi_human = "SMART / Health information", 267 .nlpi_lid = NVME_LOGPAGE_HEALTH, 268 .nlpi_csi = NVME_CSI_NVM, 269 .nlpi_vers = &nvme_vers_1v0, 270 .nlpi_kind = NVME_LOG_ID_MANDATORY, 271 .nlpi_source = NVME_LOG_DISC_S_SPEC | NVME_LOG_DISC_S_ID_CTRL, 272 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, 273 .nlpi_scope_func = nvme_lpd_health_scope, 274 .nlpi_len = sizeof (nvme_health_log_t) 275 }, { 276 .nlpi_short = "firmware", 277 .nlpi_human = "Firmware Slot Information", 278 .nlpi_lid = NVME_LOGPAGE_FWSLOT, 279 .nlpi_csi = NVME_CSI_NVM, 280 .nlpi_vers = &nvme_vers_1v0, 281 .nlpi_kind = NVME_LOG_ID_MANDATORY, 282 .nlpi_source = NVME_LOG_DISC_S_SPEC, 283 .nlpi_disc = 0, 284 .nlpi_scope = NVME_LOG_SCOPE_NVM, 285 .nlpi_len = sizeof (nvme_fwslot_log_t), 286 }, { 287 .nlpi_short = "changens", 288 .nlpi_human = "changed namespaces", 289 .nlpi_lid = NVME_LOGPAGE_NSCHANGE, 290 .nlpi_csi = NVME_CSI_NVM, 291 .nlpi_vers = &nvme_vers_1v2, 292 .nlpi_sup_func = nvme_lpd_changens_sup, 293 .nlpi_kind = NVME_LOG_ID_OPTIONAL, 294 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, 295 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, 296 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 297 .nlpi_len = sizeof (nvme_nschange_list_t) 298 }, { 299 .nlpi_short = "cmdeff", 300 .nlpi_human = "commands supported and effects", 301 .nlpi_lid = NVME_LOGPAGE_CMDSUP, 302 .nlpi_csi = NVME_CSI_NVM, 303 .nlpi_vers = &nvme_vers_1v2, 304 .nlpi_sup_func = nvme_lpd_cmdeff_sup, 305 .nlpi_kind = NVME_LOG_ID_OPTIONAL, 306 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, 307 .nlpi_scope = NVME_LOG_SCOPE_CTRL, 308 .nlpi_len = sizeof (nvme_cmdeff_log_t) 309 } }; 310 311 size_t nvme_std_log_npages = ARRAY_SIZE(nvme_std_log_pages); 312 313 nvme_log_disc_scope_t 314 nvme_log_page_info_scope(const nvme_log_page_info_t *info, 315 const nvme_valid_ctrl_data_t *data) 316 { 317 if (info->nlpi_scope_func != NULL) { 318 return (info->nlpi_scope_func(data, info)); 319 } else { 320 return (info->nlpi_scope); 321 } 322 } 323 324 uint64_t 325 nvme_log_page_info_size(const nvme_log_page_info_t *info, 326 const nvme_valid_ctrl_data_t *data, bool *var) 327 { 328 uint64_t len; 329 *var = info->nlpi_var_func != NULL; 330 331 if (info->nlpi_len_func != NULL) { 332 len = info->nlpi_len_func(data, info); 333 } else { 334 len = info->nlpi_len; 335 } 336 337 /* 338 * Some vendor-specific log pages are not documented to have 4-byte 339 * aligned lengths. This means that to get the full log page you must 340 * round this up to ensure that you end up with a valid request. We opt 341 * to do this here rather than have to check every single log page data 342 * structure and fix it up manually. While it means consumers that are 343 * using this to ignore information about the type itself may 344 * erroneously display extra bytes (e.g. nvmeadm's default hex dumper), 345 * that's better than getting an error or truncating the data. 346 */ 347 return (P2ROUNDUP(len, NVME_DWORD_SIZE)); 348 } 349 350 bool 351 nvme_log_page_info_supported(const nvme_log_page_info_t *info, 352 const nvme_valid_ctrl_data_t *data) 353 { 354 bool vers, sup_func; 355 356 if (info->nlpi_vers != NULL) { 357 vers = nvme_field_atleast(data, info->nlpi_vers); 358 } else { 359 vers = true; 360 } 361 362 if (info->nlpi_sup_func != NULL) { 363 sup_func = info->nlpi_sup_func(data, info); 364 } else { 365 sup_func = true; 366 } 367 368 return (vers && sup_func); 369 } 370