/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ #ifndef _NVME_COMMON_H #define _NVME_COMMON_H /* * Collection of common files and utilities that can be used for NVMe related * functionality. Broadly, these are meant so that the kernel and userland have * consistent validation routines. * * When we perform error checking and validation we use the kernel's set of * ioctl errors for more semantic errors. These semantic errors are translated * into ones that the library wishes to expose. Our goal is to try to use a * mostly uniform error checking framework between the two entities. * * A consumer must build nvme_version.o and nvme_field.o. Other pieces can be * added based on their needs. */ #include #include #include #ifdef __cplusplus extern "C" { #endif /* * Version related pieces from nvme_version.c. The main idea is that consumers * such as the kernel and libnvme will wrap up the nvme_vers_atleast() function * with an object that contains an NVMe version, thus reducing the likelihood * that we'll confuse versions. */ extern const nvme_version_t nvme_vers_1v0; extern const nvme_version_t nvme_vers_1v1; extern const nvme_version_t nvme_vers_1v2; extern const nvme_version_t nvme_vers_1v3; extern const nvme_version_t nvme_vers_1v4; extern const nvme_version_t nvme_vers_2v0; extern const nvme_version_t nvme_vers_2v1; extern bool nvme_vers_atleast(const nvme_version_t *, const nvme_version_t *); /* * This structure contains information about the controller that must be * supplied to the various validation functions. */ typedef struct nvme_valid_ctrl_data { const nvme_version_t *vcd_vers; const nvme_identify_ctrl_t *vcd_id; } nvme_valid_ctrl_data_t; /* * This structure is used to represent a field that is in use in a given * command. This allows us to use common validation logic for different classes * of commands such as IDENTIFY, GET LOG PAGE, etc. If everything is fine about * a field, then it should return true. Otherwise, it should return false and * fill out the error message. It is optional to override the specifics of the * nvme_ioctl_err_t with a more specific error where appropriate and known. If * it is not filled in, the validation default will be used. */ struct nvme_field_info; typedef bool (*nvme_field_sup_f)(const struct nvme_field_info *, const nvme_valid_ctrl_data_t *, char *, size_t); typedef bool (*nvme_field_valid_f)(const struct nvme_field_info *, const nvme_valid_ctrl_data_t *, uint64_t, char *, size_t); typedef struct nvme_field_info { const nvme_version_t *nlfi_vers; nvme_field_sup_f nlfi_sup; uint64_t nlfi_max_size; nvme_field_valid_f nlfi_valid; /* * Fields below this point are mostly meant to be used by libnvme and by * our printing logic, which we assume is not executed in the kernel. */ const char *nlfi_spec; const char *nlfi_human; bool nlfi_def_req; bool nlfi_def_allow; } nvme_field_info_t; typedef enum { NVME_FIELD_ERR_OK = 0, NVME_FIELD_ERR_UNSUP_VERSION, NVME_FIELD_ERR_UNSUP_FIELD, NVME_FIELD_ERR_BAD_VALUE } nvme_field_error_t; extern nvme_field_error_t nvme_field_validate(const nvme_field_info_t *, const nvme_valid_ctrl_data_t *, uint64_t, char *, size_t); /* * Various common utility routines for field validation and implementation. This * version of NSID checking treats the NSID as valid. Currently checking for the * validity of the broadcast namespace ID is left to consumers. */ extern bool nvme_field_atleast(const nvme_valid_ctrl_data_t *, const nvme_version_t *); extern bool nvme_field_valid_nsid(const nvme_field_info_t *, const nvme_valid_ctrl_data_t *, uint64_t, char *, size_t); extern bool nvme_field_range_check(const nvme_field_info_t *, uint64_t, uint64_t, char *, size_t, uint64_t); extern bool nvme_field_mask_check(const nvme_field_info_t *, uint64_t, char *, size_t, uint64_t); /* * Log page request information. The goal with these structures and fields is to * be able to validate whether something is valid, both in user/kernel context. * This phrasing also makes this much easier to unit test. Because information * is shared between libnvme and the kernel, some things are not needed for the * kernel. We do not ifdef it out for the moment, to simplify things. */ /* * This is the set of fields that the driver knows about how to validate that * can end up in an NVMe log request. Items should be added here once the kernel * knows how to put them in a log request command. */ typedef enum { NVME_LOG_REQ_FIELD_LID = 0, NVME_LOG_REQ_FIELD_LSP, NVME_LOG_REQ_FIELD_LSI, NVME_LOG_REQ_FIELD_SIZE, NVME_LOG_REQ_FIELD_CSI, NVME_LOG_REQ_FIELD_RAE, NVME_LOG_REQ_FIELD_OFFSET, NVME_LOG_REQ_FIELD_NSID } nvme_log_req_field_t; extern const nvme_field_info_t nvme_log_fields[]; extern const size_t nvme_log_nfields; /* * We now use the field based information to have a common structure to define * information about standard log pages. */ typedef struct nvme_log_page_info nvme_log_page_info_t; typedef bool (*nvme_log_page_sup_f)(const nvme_valid_ctrl_data_t *, const nvme_log_page_info_t *); typedef uint64_t (*nvme_log_page_len_f)(const nvme_valid_ctrl_data_t *, const nvme_log_page_info_t *); typedef nvme_log_disc_scope_t (*nvme_log_page_scope_f)( const nvme_valid_ctrl_data_t *, const nvme_log_page_info_t *); typedef bool (*nvme_log_page_var_len_f)(uint64_t *, const void *, size_t); struct nvme_log_page_info { const char *nlpi_short; const char *nlpi_human; const char *const *nlpi_aliases; size_t nlpi_naliases; uint32_t nlpi_lid; nvme_csi_t nlpi_csi; /* * These two entries can be used to determine whether a log page is * supported based upon its version or with a supplemental function. A * NULL item means it doesn't need to be checked. This would be the case * for vendor-specific logs. */ const nvme_version_t *nlpi_vers; const nvme_log_page_sup_f nlpi_sup_func; nvme_log_disc_kind_t nlpi_kind; nvme_log_disc_source_t nlpi_source; nvme_log_disc_fields_t nlpi_disc; /* * Log pages are valid in certain contexts. This is generally static * information, but if the scope function is implemented, we will use * that and ignore the contents of nlpi_scope. */ nvme_log_disc_scope_t nlpi_scope; nvme_log_page_scope_f nlpi_scope_func; /* * The lengths for a log page come in three forms. The first form is * ones where we can determine based on information in the controller * (or at build time) the length of the log page. Many log pages have a * fixed length or they include information in the identify controller * data structure as to their length (e.g. the error log page). To * communicate the log page's length, we will first check if * nlpi_len_func is non-NULL and call that to determine the log page * length. Otherwise we will use the value in nlpi_len. If these return * a non-zero value, the NVME_LOG_DISC_F_SIZE_FIXED will be set * automatically. * * The second form of log pages are those whose length is variable, but * we cannot determine it based on information present in the * controller. Rather we must read some amount of data from the log page * to figure this out at all. For example, many vendor specific logs * have a first uint32_t that indicates the number of valid samples and * therefore you must read that to determine the overall length of the * log page. This case follows the same path as the first case; however, * one must also set the nlpi_var_func function pointer. This results * in the NVME_LOG_DISC_F_SIZE_VAR flag being set. * * The third set of these are ones we just don't know about. In this * case, leave nlpi_len set to zero and nlpi_len_func to NULL. If this * happens or neither path returns a valid size (i.e. 0) then we will * set this to a general size that should be large enough (i.e. the * non-extended NVMe log page size) and not set either size flag. */ uint64_t nlpi_len; nvme_log_page_len_f nlpi_len_func; nvme_log_page_var_len_f nlpi_var_func; }; extern const nvme_log_page_info_t nvme_std_log_pages[]; extern const size_t nvme_std_log_npages; /* * These are functions that can be used to compute information about what's * supported and similar information that sometimes requires dynamic support. */ extern nvme_log_disc_scope_t nvme_log_page_info_scope( const nvme_log_page_info_t *, const nvme_valid_ctrl_data_t *); extern uint64_t nvme_log_page_info_size(const nvme_log_page_info_t *, const nvme_valid_ctrl_data_t *, bool *); extern bool nvme_log_page_info_supported(const nvme_log_page_info_t *, const nvme_valid_ctrl_data_t *); /* * This next section identifies the various fields that make up the NVMe * IDENTIFY command and the corresponding pieces that are in use throughout. */ typedef enum { NVME_ID_REQ_F_CNS = 0, NVME_ID_REQ_F_NSID, NVME_ID_REQ_F_CTRLID, NVME_ID_REQ_F_BUF, } nvme_identify_req_field_t; typedef enum { /* * Indicates that we allow this identify command to operate on a * namespace minor. */ NVME_IDENTIFY_INFO_F_NS_OK = 1 << 0, /* * Indicates that if we support namespace management we should attempt * to use the broadcast nsid when asking about the controller. */ NVME_IDENTIFY_INFO_F_BCAST = 1 << 1, /* * This indicates that we are performing an operation which lists * namespace IDs. As such, we don't need to validate the namespace * against the controller's list. In addition, a zero namespace ID is * allowed. */ NVME_IDENTIFY_INFO_F_NSID_LIST = 1 << 2 } nvme_identify_info_flags_t; typedef struct nvme_identify_info nvme_identify_info_t; typedef bool (*nvme_identify_sup_f)(const nvme_valid_ctrl_data_t *); struct nvme_identify_info { const char *nii_name; nvme_csi_t nii_csi; uint32_t nii_cns; const nvme_version_t *nii_vers; nvme_identify_sup_f nii_sup_func; nvme_identify_req_field_t nii_fields; nvme_identify_info_flags_t nii_flags; }; extern const nvme_field_info_t nvme_identify_fields[]; extern const size_t nvme_identify_nfields; extern const nvme_identify_info_t nvme_identify_cmds[]; extern const size_t nvme_identify_ncmds; extern bool nvme_identify_info_supported(const nvme_identify_info_t *, const nvme_valid_ctrl_data_t *); /* * NVMe Vendor Unique Commands. Note, unlike others this hasn't really changed * since it was introduced in NVMe 1.0. While libnvme wraps these up a bit to * construct commands, there is no common vendor unique command discovery * information as the kernel more or less stays out of it. */ typedef enum { NVME_VUC_REQ_FIELD_OPC = 0, NVME_VUC_REQ_FIELD_NSID, NVME_VUC_REQ_FIELD_CDW12, NVME_VUC_REQ_FIELD_CDW13, NVME_VUC_REQ_FIELD_CDW14, NVME_VUC_REQ_FIELD_CDW15, NVME_VUC_REQ_FIELD_NDT, /* * While the timeout field here is not actually part of the standard, we * require it as part of the command execution and therefore include it * in here. */ NVME_VUC_REQ_FIELD_TO } nvme_vuc_req_field_t; extern const nvme_field_info_t nvme_vuc_fields[]; extern const size_t nvme_vuc_nfields; /* * Firmware download and commit related fields and routines. */ typedef enum { NVME_FW_LOAD_REQ_FIELD_NUMD = 0, NVME_FW_LOAD_REQ_FIELD_OFFSET } nvme_fw_load_req_field_t; extern const nvme_field_info_t nvme_fw_load_fields[]; extern const size_t nvme_fw_load_nfields; extern bool nvme_fw_cmds_supported(const nvme_valid_ctrl_data_t *); extern uint32_t nvme_fw_load_granularity(const nvme_valid_ctrl_data_t *); typedef enum { NVME_FW_COMMIT_REQ_FIELD_SLOT = 0, NVME_FW_COMMIT_REQ_FIELD_ACT } nvme_fw_commit_req_field_t; extern const nvme_field_info_t nvme_fw_commit_fields[]; extern const size_t nvme_fw_commit_nfields; /* * Format NVM operations */ typedef enum { NVME_FORMAT_REQ_FIELD_LBAF = 0, NVME_FORMAT_REQ_FIELD_SES, NVME_FORMAT_REQ_FIELD_NSID } nvme_format_req_field_t; extern const nvme_field_info_t nvme_format_fields[]; extern const size_t nvme_format_nfields; extern bool nvme_format_cmds_supported(const nvme_valid_ctrl_data_t *); /* * Feature related requests */ typedef enum { NVME_GET_FEAT_REQ_FIELD_FID = 0, NVME_GET_FEAT_REQ_FIELD_SEL, NVME_GET_FEAT_REQ_FIELD_DPTR, NVME_GET_FEAT_REQ_FIELD_CDW11, NVME_GET_FEAT_REQ_FIELD_NSID } nvme_get_feat_req_field_t; extern const nvme_field_info_t nvme_get_feat_fields[]; extern const size_t nvme_get_feat_nfields; /* * Common feature information. */ typedef struct nvme_feat_info nvme_feat_info_t; typedef bool (*nvme_feat_sup_f)(const nvme_valid_ctrl_data_t *, const nvme_feat_info_t *); struct nvme_feat_info { const char *nfeat_short; const char *nfeat_spec; uint32_t nfeat_fid; /* * These three entries can be used to determine whether a feature is * supported or not based upon its version or supplemental information. */ const nvme_version_t *nfeat_vers; const nvme_feat_sup_f nfeat_sup_func; nvme_feat_kind_t nfeat_kind; /* * These describe whether the feature operates on namespaces or the * controller and misc. flags and information about them. */ nvme_feat_scope_t nfeat_scope; nvme_feat_csi_t nfeat_csi; nvme_feat_flags_t nfeat_flags; /* * These four entries describe what an NVMe device uses as input and * output fields. */ nvme_get_feat_fields_t nfeat_in_get; nvme_set_feat_fields_t nfeat_in_set; nvme_feat_output_t nfeat_out_get; nvme_feat_output_t nfeat_out_set; /* * Feature data size. This should be zero if the feature does not use a * data payload. Right now we assume the get and set sizes are identical * as that's how this normally works. */ uint64_t nfeat_len; }; extern const nvme_feat_info_t nvme_std_feats[]; extern const size_t nvme_std_nfeats; extern nvme_feat_impl_t nvme_feat_supported(const nvme_feat_info_t *, const nvme_valid_ctrl_data_t *); /* * Namespace Management and Namespace Attach Commands. * * These operations have their own sets of NVMe admin operations codes. * Separately, they then each have a means of selecting what they operate on in * dw10. Unlike other operations like Get Features or Get Log Page, these are * broken into separate ioctls in the kernel. In libnvme, namespace attach has a * single command, but namespace create and delete are treated separately. */ typedef enum { NVME_NS_CREATE_REQ_FIELD_CSI = 0, NVME_NS_CREATE_REQ_FIELD_NSZE, NVME_NS_CREATE_REQ_FIELD_NCAP, NVME_NS_CREATE_REQ_FIELD_FLBAS, NVME_NS_CREATE_REQ_FIELD_NMIC } nvme_ns_create_req_field_t; typedef enum { NVME_NS_DELETE_REQ_FIELD_NSID = 0 } nvme_ns_delete_req_field_t; /* * Strictly speaking some of these fields, such as the controller list, are * specific to the type of sub-command put into the SEL field. */ typedef enum { NVME_NS_ATTACH_REQ_FIELD_SEL = 0, NVME_NS_ATTACH_REQ_FIELD_NSID, NVME_NS_ATTACH_REQ_FIELD_DPTR } nvme_ns_attach_req_field_t; extern bool nvme_nsmgmt_cmds_supported(const nvme_valid_ctrl_data_t *); extern const nvme_field_info_t nvme_ns_attach_fields[]; extern const size_t nvme_ns_attach_nfields; extern const nvme_field_info_t nvme_ns_create_fields[]; extern const size_t nvme_ns_create_nfields; extern const nvme_field_info_t nvme_ns_delete_fields[]; extern const size_t nvme_ns_delete_nfields; /* * Allowed and required fields by CSI. */ extern const nvme_ns_create_req_field_t nvme_ns_create_fields_nvm_req[]; extern const size_t nvme_ns_create_fields_nvm_nreq; extern const nvme_ns_create_req_field_t nvme_ns_create_fields_nvm_allow[]; extern const size_t nvme_ns_create_fields_nvm_nallow; #ifdef __cplusplus } #endif #endif /* _NVME_COMMON_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * This file deals with all the knowledge related to supported, standard NVMe * features as well as validation of other requests related to features. While * there are vendor-specific features, we currently don't support issuing them * to the kernel. * * Like other parts of the common NVMe logic, we have two different sets of data * tables to help us drive validation: * * 1) We have a list of fields that are supported in the kernel ioctl interface * and libnvme for features. There are some fields like allowing a specification * via UUID which are not currently supported. The field tables are split up * among get and set features because they are somewhat different in terms of * what they allow (i.e. set features may use cdw12, cdw13, cdw15, etc.) and * because the kernel doesn't support issuing set features from userland today. * * 2) We have a table of NVMe specified required and optional features. This * table has dynamic properties related to whether things are supported and the * set of fields that are usable because some aspects of this change with the * specification version (e.g. the temperature threshold feature had no input * argument in cdw11 in NVMe 1.0). */ #include "nvme_common.h" #include #ifdef _KERNEL #include #include #else #include #include #endif static bool nvme_get_feat_supported_sel(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, char *msg, size_t msglen) { if (data->vcd_id->id_oncs.on_save != 0) { return (true); } (void) snprintf(msg, msglen, "controller does not support field %s " "(%s): missing extended data support in Log Page Attributes (LPA)", field->nlfi_human, field->nlfi_spec); return (false); } /* * An astute observer will note that there is no instance for the DPTR here. * While a buffer is required for this command, the common code does not * validate buffers. In other pieces we use a length as a proxy for checking the * buffer; however, there is no length argument here. The buffer is expected by * the controller to be of sufficient size. This is validated by the kernel in * nvme_validate_get_feature(). */ const nvme_field_info_t nvme_get_feat_fields[] = { [NVME_GET_FEAT_REQ_FIELD_FID] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = NVME_FEAT_MAX_FID, .nlfi_spec = "fid", .nlfi_human = "feature identifier", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_GET_FEAT_REQ_FIELD_SEL] = { .nlfi_vers = &nvme_vers_1v1, .nlfi_sup = nvme_get_feat_supported_sel, .nlfi_max_size = NVME_FEAT_MAX_SEL, .nlfi_spec = "sel", .nlfi_human = "select", /* * Because this field was introduced in NVMe 1.1 and because * most of the time we want to assume folks are looking for the * current value, we end up opting to make this a non-required * field and default to getting the current value. */ .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_GET_FEAT_REQ_FIELD_CDW11] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = UINT32_MAX, .nlfi_spec = "cdw11", .nlfi_human = "control dword 11", /* * While this isn't required by default, we will end up setting * it as required based on the specifics of the feature and its * version. */ .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_GET_FEAT_REQ_FIELD_NSID] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_field_valid_nsid, .nlfi_spec = "nsid", .nlfi_human = "namespace ID", .nlfi_def_req = false, .nlfi_def_allow = true } }; const size_t nvme_get_feat_nfields = ARRAY_SIZE(nvme_get_feat_fields); static bool nvme_feat_write_cache_sup(const nvme_valid_ctrl_data_t *data, const nvme_feat_info_t *feat) { return (data->vcd_id->id_vwc.vwc_present != 0); } static bool nvme_feat_apst_sup(const nvme_valid_ctrl_data_t *data, const nvme_feat_info_t *feat) { return (data->vcd_id->id_apsta.ap_sup != 0); } /* * Note, many of these short names come from the history of nvmeadm(8). If you * wish to change them, then you must figure out a way to make sure we can still * honor the original names. Most fields here try to use a value of 0 as * reasonable default so if something's not specified we'll get a reasonable * value. For example, NVME_FEAT_MANDATORY, NVME_FEAT_CSI_NONE, etc. all have a * value of zero so when that field isn't present we get something reasonable. * This leads us to generally define fields that are exceptions to the norm * (e.g. when a feature is specific to the NVM feature set). */ const nvme_feat_info_t nvme_std_feats[] = { { .nfeat_short = "arb", .nfeat_spec = "Arbitration", .nfeat_fid = NVME_FEAT_ARBITRATION, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "pm", .nfeat_spec = "Power Management", .nfeat_fid = NVME_FEAT_POWER_MGMT, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "range", .nfeat_spec = "LBA Range Type", .nfeat_fid = NVME_FEAT_LBA_RANGE, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_OPTIONAL, .nfeat_scope = NVME_FEAT_SCOPE_NS, .nfeat_csi = NVME_FEAT_CSI_NVM, .nfeat_in_get = NVME_GET_FEAT_F_NSID | NVME_GET_FEAT_F_DATA, .nfeat_in_set = NVME_SET_FEAT_F_NSID | NVME_SET_FEAT_F_CDW11 | NVME_SET_FEAT_F_DATA, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 | NVME_FEAT_OUTPUT_DATA, .nfeat_len = NVME_LBA_RANGE_BUFSIZE }, { .nfeat_short = "temp", .nfeat_spec = "Temperature Threshold", .nfeat_fid = NVME_FEAT_TEMPERATURE, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, /* * In NVMe 1.0 and NVMe 1.1, there was only a single temperature sensor * that the spec defined and was present in the threshold feature. * However, starting in NVMe 1.2, this was changed so that a sensor was * required to be specified in NVMe 1.2 to identify the sensor. As such * we always end up saying that this is required. */ .nfeat_in_get = NVME_GET_FEAT_F_CDW11, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "errrec", .nfeat_spec = "Error Recovery", .nfeat_fid = NVME_FEAT_ERROR, .nfeat_vers = &nvme_vers_1v0, .nfeat_csi = NVME_FEAT_CSI_NVM, .nfeat_kind = NVME_FEAT_MANDATORY, /* * The scope of this feature has a bit of a complicated history. * Originally we always got this on the controller and that works for * most NVMe 1.0-1.2 devices. The introduction of both namespace * management and of the DULBE option which is namespace specific, made * this more nuanced. The NVMe 1.4 specification makes it clear that * this is namespace specific; however, if we ask for this feature on * many NVMe 1.3 devices with namespace support and some NVMe 1.2, it'll * generate an error about missing namespace information. Unfortunately * namespace management is not a good proxy for this as for example the * Samsung 980 Pro is an NVMe 1.3 device without namespace management * and it will error with invalid namespace if we specify zeros. * * However, most devices that we've surveyed will always answer a GET * FEATURES request with a namespace specified. Therefore, given the * changes that have happened, for now we're going to phrase it scoped * to a namespace and requiring a namespace ID. */ .nfeat_scope = NVME_FEAT_SCOPE_NS, .nfeat_in_get = NVME_GET_FEAT_F_NSID, .nfeat_in_set = NVME_SET_FEAT_F_NSID | NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "cache", .nfeat_spec = "Volatile Write Cache", .nfeat_fid = NVME_FEAT_WRITE_CACHE, .nfeat_sup_func = nvme_feat_write_cache_sup, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_OPTIONAL, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "queues", .nfeat_spec = "Number of Queues", .nfeat_fid = NVME_FEAT_NQUEUES, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { /* * The interrupt coalescing and the interrupt vector configuration * features are required for all PCIe controllers; however, they are not * supported for other types of controllers. As we only support NVMe * PCIe controllers with this library right now we don't do anything * special to denote that. If we do, we will probably want to create an * optional function for determining the kind of feature and leverage * the existing nfeat_sup_func. */ .nfeat_short = "coalescing", .nfeat_spec = "Interrupt Coalescing", .nfeat_fid = NVME_FEAT_INTR_COAL, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "vector", .nfeat_spec = "Interrupt Vector Configuration", .nfeat_fid = NVME_FEAT_INTR_VECT, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_get = NVME_GET_FEAT_F_CDW11, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "atomicity", .nfeat_spec = "Write Atomicity", .nfeat_fid = NVME_FEAT_WRITE_ATOM, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "event", .nfeat_spec = "Asynchronous Event Configuration", .nfeat_fid = NVME_FEAT_ASYNC_EVENT, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_MANDATORY, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "apst", .nfeat_spec = "Autonomous Power State Transition", .nfeat_fid = NVME_FEAT_AUTO_PST, .nfeat_vers = &nvme_vers_1v1, .nfeat_sup_func = nvme_feat_apst_sup, .nfeat_kind = NVME_FEAT_OPTIONAL, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_get = NVME_GET_FEAT_F_DATA, .nfeat_in_set = NVME_SET_FEAT_F_CDW11 | NVME_SET_FEAT_F_DATA, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 | NVME_FEAT_OUTPUT_DATA, .nfeat_len = NVME_AUTO_PST_BUFSIZE }, { .nfeat_short = "progress", .nfeat_spec = "Software Progress Marker", .nfeat_fid = NVME_FEAT_PROGRESS, .nfeat_vers = &nvme_vers_1v0, .nfeat_kind = NVME_FEAT_OPTIONAL, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_set = NVME_SET_FEAT_F_CDW11, .nfeat_out_get = NVME_FEAT_OUTPUT_CDW0 }, { .nfeat_short = "hostsup", .nfeat_spec = "Host Behavior Support", .nfeat_fid = NVME_FEAT_HOST_BEHAVE, .nfeat_vers = &nvme_vers_1v4, /* * There is no specific way to tell if this feature is supported other * than to see if all of the various behaviors that a host could enable * are supported through various means. */ .nfeat_kind = NVME_FEAT_OPTIONAL, .nfeat_scope = NVME_FEAT_SCOPE_CTRL, .nfeat_in_get = NVME_GET_FEAT_F_DATA, .nfeat_in_set = NVME_SET_FEAT_F_DATA, .nfeat_out_get = NVME_FEAT_OUTPUT_DATA, .nfeat_len = sizeof (nvme_host_behavior_t) } }; const size_t nvme_std_nfeats = ARRAY_SIZE(nvme_std_feats); /* * Now it's time to answer the only hard question here: is this feature actually * supported by the controller. Prior to NVMe 2.x and the Feature Identifiers * Supported and Effects page, we have to use a heuristic for this. Our * heuristics rules are as follows: * * 1) If this is a vendor-specific feature that we have identified is present on * this controller based on a datasheet, we assume it's present. * * 2) If the feature was introduced in an NVMe spec version newer than our * controller, then it's clearly unsupported. * * 3) If it is a mandatory feature, we have the right controller type, and we * are past the minimum version, then this is supported. * * 4) If the feature is optional and has an explicit feature bit that indicates * whether it's present or not, then we can use that to determine if it's * implemented or not. * * Otherwise we must conclude that we don't know. */ nvme_feat_impl_t nvme_feat_supported(const nvme_feat_info_t *info, const nvme_valid_ctrl_data_t *data) { if (info->nfeat_kind == NVME_FEAT_VENDOR_SPECIFIC) { return (NVME_FEAT_IMPL_SUPPORTED); } if (info->nfeat_vers != NULL && !nvme_vers_atleast(data->vcd_vers, info->nfeat_vers)) { return (NVME_FEAT_IMPL_UNSUPPORTED); } if (info->nfeat_kind == NVME_FEAT_MANDATORY) { ASSERT3P(info->nfeat_sup_func, ==, NULL); return (NVME_FEAT_IMPL_SUPPORTED); } if (info->nfeat_sup_func != NULL) { if (info->nfeat_sup_func(data, info)) { return (NVME_FEAT_IMPL_SUPPORTED); } return (NVME_FEAT_IMPL_UNSUPPORTED); } return (NVME_FEAT_IMPL_UNKNOWN); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * This file contains shared pieces of the NVMe field validation logic and has * shared pieces that are used between different parts. */ #include "nvme_common.h" #include #ifdef _KERNEL #include #include #else #include #include #endif bool nvme_field_atleast(const nvme_valid_ctrl_data_t *data, const nvme_version_t *targ) { return (nvme_vers_atleast(data->vcd_vers, targ)); } /* * Note, we rely on external logic to determine if the broadcast nsid is valid. * We always accept it. */ bool nvme_field_valid_nsid(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t nsid, char *msg, size_t msglen) { if ((nsid != 0 && nsid <= data->vcd_id->id_nn) || nsid == NVME_NSID_BCAST) { return (true); } (void) snprintf(msg, msglen, "namespace id %" PRIu64 "is outside the " "valid range [0x%x, 0x%x], the broadcast nsid (0x%x) may be valid", nsid, NVME_NSID_MIN, NVME_NSID_BCAST, data->vcd_id->id_nn); return (false); } bool nvme_field_range_check(const nvme_field_info_t *field, uint64_t min, uint64_t max, char *msg, size_t msglen, uint64_t value) { if (value >= min && value <= max) { return (true); } (void) snprintf(msg, msglen, "field %s (%s) value 0x%" PRIx64 " is outside the valid range: [0x%" PRIx64 ", 0x%" PRIx64 "]", field->nlfi_human, field->nlfi_spec, value, min, max); return (false); } bool nvme_field_mask_check(const nvme_field_info_t *field, uint64_t valid_mask, char *msg, size_t msglen, uint64_t value) { uint64_t inval = ~valid_mask & value; if (inval == 0) { return (true); } (void) snprintf(msg, msglen, "field %s (%s) value 0x%" PRIx64 " uses bits outside of the mask 0x%" PRIx64 ": 0x%" PRIx64, field->nlfi_human, field->nlfi_spec, value, valid_mask, inval); return (false); } /* * This is a general validation function for fields that are part of a command. * It will check if the field is supported by the controller and if so, that its * value is within the expected range. On error, an optional message will be * written that explains the error. This is intended to be shared between * userland and the kernel. The kernel should pass NULL/0 for msg/msglen because * there is no message translation capability in the kernel. */ nvme_field_error_t nvme_field_validate(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t value, char *msg, size_t msglen) { ASSERT3P(field->nlfi_vers, !=, NULL); if (msglen > 0) *msg = '\0'; if (!nvme_field_atleast(data, field->nlfi_vers)) { (void) snprintf(msg, msglen, "field %s (%s) requires " "version %u.%u, but device is at %u.%u", field->nlfi_human, field->nlfi_spec, data->vcd_vers->v_major, data->vcd_vers->v_minor, field->nlfi_vers->v_major, field->nlfi_vers->v_minor); return (NVME_FIELD_ERR_UNSUP_VERSION); } if (field->nlfi_sup != NULL && !field->nlfi_sup(field, data, msg, msglen)) { (void) snprintf(msg, msglen, "field %s (%s) is not " "supported by the controller", field->nlfi_human, field->nlfi_spec); return (NVME_FIELD_ERR_UNSUP_FIELD); } if (field->nlfi_valid != NULL) { if (!field->nlfi_valid(field, data, value, msg, msglen)) { if (msglen > 0 && *msg == '\0') { (void) snprintf(msg, msglen, "field %s (%s) value 0x%" PRIx64 " is invalid", field->nlfi_human, field->nlfi_spec, value); } return (NVME_FIELD_ERR_BAD_VALUE); } } else if (!nvme_field_range_check(field, 0, field->nlfi_max_size, msg, msglen, value)) { return (NVME_FIELD_ERR_BAD_VALUE); } return (NVME_FIELD_ERR_OK); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Common field and validation for NVMe firmware related pieces. */ #include "nvme_common.h" #include #ifdef _KERNEL #include #include #else #include #include #endif /* * The default granularity we enforce prior to the 1.3 spec's introduction of * the FWUG (firmware update granularity). */ #define NVME_DEFAULT_FWUG 4096 /* * The FWUG is in multiples of 4 KiB. */ #define NVME_FWUG_MULT 4096 /* * Answers the question of are firmware commands supported or not in a way * that is a bit easier for us to unit test. */ bool nvme_fw_cmds_supported(const nvme_valid_ctrl_data_t *data) { return (data->vcd_id->id_oacs.oa_firmware != 0); } /* * Validate a length/offset for an NVMe firmware download request. * These fields in the NVMe specification are in units of uint32_t values but, * since the ioctl interfaces deal with byte counts, so do these validation * functions. According to the specification the same constraints hold for * both the length and offset fields; experience has shown, however, that we * need to be more relaxed when validating the length -- see the comment in the * validation function below. * * Starting in NVMe 1.3, additional constraints about the granularity were * added through the FWUG field in the identify controller data structure. This * indicates the required alignment in 4 KiB chunks. The controller is allowed * to indicate a value of 0 to indicate that this is unknown (which is not * particularly helpful) or that it may be 0xff which indicates that there is * no alignment constraint other than the natural uint32_t alignment. * * For devices that exist prior to NVMe 1.3, we assume that we probably need at * least 4 KiB granularity for the time being. This may need to change in the * future. */ uint32_t nvme_fw_load_granularity(const nvme_valid_ctrl_data_t *data) { uint32_t gran = NVME_DEFAULT_FWUG; if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) { const uint8_t fwug = data->vcd_id->ap_fwug; if (fwug == 0xff) { gran = NVME_DWORD_SIZE; } else if (fwug != 0) { gran = fwug * NVME_FWUG_MULT; } } return (gran); } static bool nvme_fw_load_field_valid_len(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t len, char *msg, size_t msglen) { /* * While we would like to validate that the length is consistent with * the firmware upgrade granularity, we have encountered drives where * the vendor's firmware update file sizes are not a multiple of the * required granularity, and where the strategy of padding the last * block out to that required granularity does not always result in a * file that the drive will accept. * * The best we can do is ensure that it is a whole number of dwords. */ if ((len & NVME_DWORD_MASK) != 0) { (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must " "be aligned to the firmware update granularity 0x%x", field->nlfi_human, field->nlfi_spec, len, NVME_DWORD_SIZE); return (false); } return (nvme_field_range_check(field, NVME_DWORD_SIZE, NVME_FW_LENB_MAX, msg, msglen, len)); } static bool nvme_fw_load_field_valid_offset(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t off, char *msg, size_t msglen) { uint32_t gran = nvme_fw_load_granularity(data); if ((off % gran) != 0) { (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must " "be aligned to the firmware update granularity 0x%x", field->nlfi_human, field->nlfi_spec, off, gran); return (false); } return (nvme_field_range_check(field, 0, NVME_FW_OFFSETB_MAX, msg, msglen, off)); } const nvme_field_info_t nvme_fw_load_fields[] = { [NVME_FW_LOAD_REQ_FIELD_NUMD] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_fw_load_field_valid_len, .nlfi_spec = "numd", .nlfi_human = "number of dwords", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_FW_LOAD_REQ_FIELD_OFFSET] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_fw_load_field_valid_offset, .nlfi_spec = "ofst", .nlfi_human = "offset", .nlfi_def_req = true, .nlfi_def_allow = true } }; const size_t nvme_fw_load_nfields = ARRAY_SIZE(nvme_fw_load_fields); static bool nvme_fw_commit_field_valid_slot(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t slot, char *msg, size_t msglen) { return (nvme_field_range_check(field, NVME_FW_SLOT_MIN, data->vcd_id->id_frmw.fw_nslot, msg, msglen, slot)); } /* * This validation function represents an area of improvement that we'd like to * figure out in the future. Immediate firmware activations are only supported * in NVMe 1.3, so while it's a bad value prior to NVMe 1.3, that is a somewhat * confusing error. In addition, the various boot partition updates are not * supported, so it's not a bad value to the spec, but just to us. */ static bool nvme_fw_commit_field_valid_act(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t act, char *msg, size_t msglen) { uint64_t max = NVME_FWC_ACTIVATE; if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) { max = NVME_FWC_ACTIVATE_IMMED; } return (nvme_field_range_check(field, 0, max, msg, msglen, act)); } const nvme_field_info_t nvme_fw_commit_fields[] = { [NVME_FW_COMMIT_REQ_FIELD_SLOT] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_fw_commit_field_valid_slot, .nlfi_spec = "fs", .nlfi_human = "firmware slot", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_FW_COMMIT_REQ_FIELD_ACT] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_fw_commit_field_valid_act, .nlfi_spec = "ca", .nlfi_human = "commit action", .nlfi_def_req = true, .nlfi_def_allow = true } }; const size_t nvme_fw_commit_nfields = ARRAY_SIZE(nvme_fw_commit_fields); /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Common field and validation for NVMe Format NVM operations. This covers what * nvmeadm(8) calls both secure-erase and format. */ #include "nvme_common.h" #include const nvme_field_info_t nvme_format_fields[] = { [NVME_FORMAT_REQ_FIELD_LBAF] = { .nlfi_vers = &nvme_vers_1v0, /* * In the future we should plumb through enough information that * we can check this against the common namespace information. */ .nlfi_max_size = NVME_FRMT_MAX_LBAF, .nlfi_spec = "lbaf", .nlfi_human = "LBA format", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_FORMAT_REQ_FIELD_SES] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = NVME_FRMT_MAX_SES, .nlfi_spec = "ses", .nlfi_human = "secure erase settings", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_FORMAT_REQ_FIELD_NSID] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_field_valid_nsid, .nlfi_spec = "nsid", .nlfi_human = "namespace ID", .nlfi_def_req = false, .nlfi_def_allow = true } }; const size_t nvme_format_nfields = ARRAY_SIZE(nvme_format_fields); bool nvme_format_cmds_supported(const nvme_valid_ctrl_data_t *data) { return (data->vcd_id->id_oacs.oa_format != 0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Information about supported NVMe identify commands that can be issued. */ #include "nvme_common.h" #include static bool nvme_identify_field_valid_cns(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t cns, char *msg, size_t msglen) { uint64_t max; if (nvme_field_atleast(data, &nvme_vers_1v2)) { max = NVME_IDENTIFY_MAX_CNS_1v2; } else if (nvme_field_atleast(data, &nvme_vers_1v1)) { max = NVME_IDENTIFY_MAX_CNS_1v1; } else { max = NVME_IDENTIFY_MAX_CNS; } return (nvme_field_range_check(field, 0, max, msg, msglen, cns)); } static bool nvme_identify_field_valid_buf(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t len, char *msg, size_t msglen) { return (nvme_field_range_check(field, NVME_IDENTIFY_BUFSIZE, NVME_IDENTIFY_BUFSIZE, msg, msglen, len)); } const nvme_field_info_t nvme_identify_fields[] = { [NVME_ID_REQ_F_CNS] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_identify_field_valid_cns, .nlfi_spec = "cns", .nlfi_human = "Controller or Namespace Structure", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_ID_REQ_F_NSID] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_spec = "nsid", /* * The NSID for an identify command can have several different * forms. */ .nlfi_max_size = NVME_IDENTIFY_MAX_NSID, .nlfi_human = "namespace ID", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_ID_REQ_F_CTRLID] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_max_size = NVME_IDENTIFY_MAX_CTRLID, .nlfi_spec = "cntid", .nlfi_human = "Controller ID", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_ID_REQ_F_BUF] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_identify_field_valid_buf, .nlfi_spec = "dptr", .nlfi_human = "output", .nlfi_def_req = true, .nlfi_def_allow = true } }; const size_t nvme_identify_nfields = ARRAY_SIZE(nvme_identify_fields); static bool nvme_identify_support_nsid(const nvme_valid_ctrl_data_t *data) { return (data->vcd_id->id_oacs.oa_nsmgmt != 0); } const nvme_identify_info_t nvme_identify_cmds[] = { { .nii_name = "identify namespace", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_NSID, .nii_vers = &nvme_vers_1v0, .nii_fields = 1 << NVME_ID_REQ_F_NSID, .nii_flags = NVME_IDENTIFY_INFO_F_NS_OK | NVME_IDENTIFY_INFO_F_BCAST }, { .nii_name = "identify controller", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_CTRL, .nii_vers = &nvme_vers_1v0, }, { .nii_name = "active namespace ID list", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_NSID_LIST, .nii_vers = &nvme_vers_1v1, .nii_fields = 1 << NVME_ID_REQ_F_NSID, .nii_flags = NVME_IDENTIFY_INFO_F_NSID_LIST }, { .nii_name = "namespace identification descriptor list", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_NSID_DESC, .nii_vers = &nvme_vers_1v3, .nii_fields = (1 << NVME_ID_REQ_F_NSID), .nii_flags = NVME_IDENTIFY_INFO_F_NS_OK }, { .nii_name = "allocated namespace id list", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_NSID_ALLOC_LIST, .nii_vers = &nvme_vers_1v2, .nii_sup_func = nvme_identify_support_nsid, .nii_fields = 1 << NVME_ID_REQ_F_NSID, .nii_flags = NVME_IDENTIFY_INFO_F_NSID_LIST }, { .nii_name = "identify allocated namespace", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_NSID_ALLOC, .nii_vers = &nvme_vers_1v2, .nii_sup_func = nvme_identify_support_nsid, .nii_fields = 1 << NVME_ID_REQ_F_NSID, .nii_flags = NVME_IDENTIFY_INFO_F_NS_OK }, { .nii_name = "namespace attached controller list", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_NSID_CTRL_LIST, .nii_vers = &nvme_vers_1v2, .nii_sup_func = nvme_identify_support_nsid, .nii_fields = (1 << NVME_ID_REQ_F_NSID) | (1 << NVME_ID_REQ_F_CTRLID), .nii_flags = NVME_IDENTIFY_INFO_F_NS_OK }, { .nii_name = "nvm subsystem controller list", .nii_csi = NVME_CSI_NVM, .nii_cns = NVME_IDENTIFY_CTRL_LIST, .nii_vers = &nvme_vers_1v2, .nii_sup_func = nvme_identify_support_nsid, .nii_fields = (1 << NVME_ID_REQ_F_CTRLID) } }; const size_t nvme_identify_ncmds = ARRAY_SIZE(nvme_identify_cmds); bool nvme_identify_info_supported(const nvme_identify_info_t *info, const nvme_valid_ctrl_data_t *data) { if (info->nii_vers != NULL && !nvme_field_atleast(data, info->nii_vers)) { return (false); } if (info->nii_sup_func != NULL && !info->nii_sup_func(data)) { return (false); } return (true); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2026 Oxide Computer Company */ /* * This file deals with validating and issuing various log page requests to an * NVMe target. This contains information about all spec-based log pages. The * get log page command has added a number of fields that have evolved over * time. We validate that we're only sending commands to a device that we expect * it to have a chance of understanding. In general, we only allow through * unknown log pages that correspond to vendor-specific commands. * * We have two different tables of information that we use to drive and validate * things here: * * 1) We have a list of fields that exist which include minimum controller * versions and related functionality validation routines that operate off of * the nvme_t. While this list includes things like the CSI and LID, these are * things that may only be specified when we have a non-standard log page. * * 2) We then have a table of log pages that are supported which list which * fields we allow for the device. Not all of this can be static. * * This file has been designed to be shareable between both userland and the * kernel since the logic that libnvme wants to use is quite similar. */ #include "nvme_common.h" #include #ifdef _KERNEL #include #include #else #include #include #include #endif static bool nvme_log_field_valid_lsp(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t lsp, char *msg, size_t msglen) { uint64_t max; if (nvme_field_atleast(data, &nvme_vers_2v0)) { max = NVME_LOG_MAX_LSP_2v0; } else { max = NVME_LOG_MAX_LSP; } return (nvme_field_range_check(field, 0, max, msg, msglen, lsp)); } static bool nvme_log_field_supported_offset(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, char *msg, size_t msglen) { if (data->vcd_id->id_lpa.lp_extsup != 0) { return (true); } (void) snprintf(msg, msglen, "controller does not support field %s " "(%s): missing extended data support in Log Page Attributes (LPA)", field->nlfi_human, field->nlfi_spec); return (false); } /* * The offset is a full 64-bit byte value; however, it must be 4-byte aligned. */ static bool nvme_log_field_valid_offset(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen) { if ((size % NVME_DWORD_SIZE) != 0) { (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is " "invalid: value must be %u-byte aligned", field->nlfi_human, field->nlfi_spec, size, NVME_DWORD_SIZE); return (false); } return (true); } static bool nvme_log_field_valid_size(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen) { uint64_t max = NVME_LOG_MAX_SIZE; if (nvme_field_atleast(data, &nvme_vers_1v2) && data->vcd_id->id_lpa.lp_extsup != 0) { max = NVME_LOG_MAX_SIZE_1v2; } /* * The NVMe specification operates in terms of uint32_t (dword) units. * Make sure that we are operating within that constraint. */ if ((size % 4) != 0) { (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is " "invalid: value must be 4-byte aligned", field->nlfi_human, field->nlfi_spec, size); return (false); } return (nvme_field_range_check(field, 4, max, msg, msglen, size)); } const nvme_field_info_t nvme_log_fields[] = { [NVME_LOG_REQ_FIELD_LID] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = NVME_LOG_MAX_LID, .nlfi_spec = "lid", .nlfi_human = "log ID", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_LOG_REQ_FIELD_LSP] = { .nlfi_vers = &nvme_vers_1v3, .nlfi_valid = nvme_log_field_valid_lsp, .nlfi_spec = "lsp", .nlfi_human = "log specific field", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_LOG_REQ_FIELD_LSI] = { .nlfi_vers = &nvme_vers_1v4, .nlfi_max_size = NVME_LOG_MAX_LSI, .nlfi_spec = "lsi", .nlfi_human = "log specific ID", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_LOG_REQ_FIELD_SIZE] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_log_field_valid_size, .nlfi_spec = "dptr/numd", .nlfi_human = "output", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_LOG_REQ_FIELD_CSI] = { .nlfi_vers = &nvme_vers_2v0, /* * This has the field's maximum range right now, though NVMe 2.0 * only defines a few values. Because the kernel only allows * through known log pages, we don't really bother to check the * condensed range and allow vendor-specific logs to go wild. */ .nlfi_max_size = NVME_LOG_MAX_CSI, .nlfi_spec = "csi", .nlfi_human = "command set ID", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_LOG_REQ_FIELD_RAE] = { .nlfi_vers = &nvme_vers_1v3, .nlfi_max_size = NVME_LOG_MAX_RAE, .nlfi_spec = "rae", .nlfi_human = "retain asynchronous event", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_LOG_REQ_FIELD_OFFSET] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_sup = nvme_log_field_supported_offset, .nlfi_valid = nvme_log_field_valid_offset, .nlfi_max_size = NVME_LOG_MAX_OFFSET, .nlfi_spec = "lpo", .nlfi_human = "log offset", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_LOG_REQ_FIELD_NSID] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_field_valid_nsid, .nlfi_spec = "nsid", .nlfi_human = "namespace ID", .nlfi_def_req = false, .nlfi_def_allow = true } }; const size_t nvme_log_nfields = ARRAY_SIZE(nvme_log_fields); static uint64_t nvme_lpd_error_len(const nvme_valid_ctrl_data_t *data, const nvme_log_page_info_t *lpi) { const uint64_t nents = data->vcd_id->id_elpe + 1; const uint64_t logsz = nents * sizeof (nvme_error_log_entry_t); return (logsz); } static nvme_log_disc_scope_t nvme_lpd_health_scope(const nvme_valid_ctrl_data_t *data, const nvme_log_page_info_t *lpi) { nvme_log_disc_scope_t ret = NVME_LOG_SCOPE_CTRL; if (nvme_field_atleast(data, &nvme_vers_1v0) && data->vcd_id->id_lpa.lp_smart != 0) { ret |= NVME_LOG_SCOPE_NS; } return (ret); } static bool nvme_lpd_changens_sup(const nvme_valid_ctrl_data_t *data, const nvme_log_page_info_t *lpi) { return (nvme_field_atleast(data, &nvme_vers_1v2) && data->vcd_id->id_oaes.oaes_nsan != 0); } static bool nvme_lpd_cmdeff_sup(const nvme_valid_ctrl_data_t *data, const nvme_log_page_info_t *lpi) { return (nvme_field_atleast(data, &nvme_vers_1v2) && data->vcd_id->id_lpa.lp_cmdeff != 0); } static bool nvme_lpd_pev_sup(const nvme_valid_ctrl_data_t *data, const nvme_log_page_info_t *lpi) { return (nvme_field_atleast(data, &nvme_vers_1v4) && data->vcd_id->id_lpa.lp_persist != 0); } static bool nvme_lpd_pev_len(uint64_t *outp, const void *data, size_t len) { nvme_pev_log_t pev; if (len < sizeof (pev)) { return (false); } (void) memcpy(&pev, data, sizeof (pev)); *outp = pev.pel_tll; return (true); } static bool nvme_lpd_telemetry_sup(const nvme_valid_ctrl_data_t *data, const nvme_log_page_info_t *lpi) { return (nvme_field_atleast(data, &nvme_vers_1v3) && data->vcd_id->id_lpa.lp_telemetry != 0); } static bool nvme_lpd_telemetry_len(uint64_t *outp, const void *data, size_t len) { nvme_telemetry_log_t telem; uint64_t nblks; if (len < sizeof (telem)) { return (false); } (void) memcpy(&telem, data, sizeof (telem)); /* * See if we have section 4 information, then fall back to 3, 2, and * finally 1 to determine the size. We then have to add the header. */ nblks = telem.ntl_thda4lb; if (nblks == 0) nblks = telem.ntl_thda3lb; if (nblks == 0) nblks = telem.ntl_thda2lb; if (nblks == 0) nblks = telem.ntl_thda1lb; nblks++; *outp = nblks * sizeof (telem); return (true); } static bool nvme_lpd_eom_len(uint64_t *outp, const void *data, size_t len) { nvme_eom_hdr_t hdr; uint64_t out; if (len < sizeof (hdr)) { return (false); } (void) memcpy(&hdr, data, sizeof (hdr)); /* * The valid size depends on the value in EOMIP. If the measurement is * not done then the only amount that is valid is the overall header * length. Currently the header length is required to be 64 bytes. If it * is not that, then we will generate an error as that means something * else is going on that means we shouldn't trust this. */ if (hdr.eom_hsize != sizeof (hdr)) { return (false); } out = sizeof (hdr); switch (hdr.eom_eomip) { case NVME_EOM_NOT_STARTED: case NVME_EOM_IN_PROGRESS: break; case NVME_EOM_DONE: out += hdr.eom_ds * hdr.eom_nd; break; default: /* * This indicates we have a progress code we don't know how to * handle. It's possible that it would make sense to just return * the header size, but for now we opt to be conservative and * simply fail. */ return (false); } *outp = out; return (true); } /* * Short names have evolved a bit over time. When they do we put the old name as * an alias record here. */ static const char *nvme_supcmd_aliases[] = { "cmdeff" }; /* * The short names here correspond to the well defined names in nvmeadm(8) and * libnvme(3LIB) that users expect to be able to use. Please do not change them * without accounting for aliases and backwards compatibility. The index of the * supported log pages entry is expected to be the first entry here. This is * relied upon by log discovery in nvme_log_discover_fetch_sup_logs(). */ const nvme_log_page_info_t nvme_std_log_pages[] = { { .nlpi_short = "suplog", .nlpi_human = "Supported Log Pages", .nlpi_lid = NVME_LOGPAGE_SUP, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_2v0, .nlpi_kind = NVME_LOG_ID_MANDATORY, .nlpi_source = NVME_LOG_DISC_S_SPEC, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len = sizeof (nvme_suplog_log_t) }, { .nlpi_short = "error", .nlpi_human = "Error information", .nlpi_lid = NVME_LOGPAGE_ERROR, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_1v0, .nlpi_kind = NVME_LOG_ID_MANDATORY, .nlpi_source = NVME_LOG_DISC_S_SPEC, .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len_func = nvme_lpd_error_len }, { .nlpi_short = "health", .nlpi_human = "SMART / Health information", .nlpi_lid = NVME_LOGPAGE_HEALTH, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_1v0, .nlpi_kind = NVME_LOG_ID_MANDATORY, .nlpi_source = NVME_LOG_DISC_S_SPEC | NVME_LOG_DISC_S_ID_CTRL, .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, .nlpi_scope_func = nvme_lpd_health_scope, .nlpi_len = sizeof (nvme_health_log_t) }, { .nlpi_short = "firmware", .nlpi_human = "Firmware Slot Information", .nlpi_lid = NVME_LOGPAGE_FWSLOT, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_1v0, .nlpi_kind = NVME_LOG_ID_MANDATORY, .nlpi_source = NVME_LOG_DISC_S_SPEC, .nlpi_disc = 0, .nlpi_scope = NVME_LOG_SCOPE_NVM, .nlpi_len = sizeof (nvme_fwslot_log_t), }, { .nlpi_short = "changens", .nlpi_human = "changed namespaces", .nlpi_lid = NVME_LOGPAGE_NSCHANGE, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_1v2, .nlpi_sup_func = nvme_lpd_changens_sup, .nlpi_kind = NVME_LOG_ID_OPTIONAL, .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len = sizeof (nvme_nschange_list_t) }, { .nlpi_short = "supcmd", .nlpi_human = "commands supported and effects", .nlpi_aliases = nvme_supcmd_aliases, .nlpi_naliases = ARRAY_SIZE(nvme_supcmd_aliases), .nlpi_lid = NVME_LOGPAGE_CMDSUP, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_1v2, .nlpi_sup_func = nvme_lpd_cmdeff_sup, .nlpi_kind = NVME_LOG_ID_OPTIONAL, .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len = sizeof (nvme_cmdeff_log_t) }, { .nlpi_short = "pev", .nlpi_human = "persistent event log", .nlpi_lid = NVME_LOGPAGE_PEV, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_1v4, .nlpi_sup_func = nvme_lpd_pev_sup, .nlpi_kind = NVME_LOG_ID_OPTIONAL, .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, .nlpi_disc = NVME_LOG_DISC_F_NEED_LSP, .nlpi_scope = NVME_LOG_SCOPE_NVM, .nlpi_len = sizeof (nvme_pev_log_t), .nlpi_var_func = nvme_lpd_pev_len }, { .nlpi_short = "telemetry", .nlpi_human = "telemetry host-initiated", .nlpi_lid = NVME_LOGPAGE_TELMHOST, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_1v3, .nlpi_sup_func = nvme_lpd_telemetry_sup, .nlpi_kind = NVME_LOG_ID_OPTIONAL, .nlpi_source = NVME_LOG_DISC_S_ID_CTRL, .nlpi_disc = NVME_LOG_DISC_F_NEED_LSP, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len = sizeof (nvme_telemetry_log_t), .nlpi_var_func = nvme_lpd_telemetry_len }, { .nlpi_short = "supfeat", .nlpi_human = "Feature Identifiers Supported and Effects", .nlpi_lid = NVME_LOGPAGE_SUPFEAT, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_2v0, .nlpi_kind = NVME_LOG_ID_MANDATORY, .nlpi_source = NVME_LOG_DISC_S_SPEC, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len = sizeof (nvme_supfeat_log_t) }, { .nlpi_short = "supmicmd", .nlpi_human = "NVMe-MI Commands Supported and Effects", .nlpi_lid = NVME_LOGPAGE_SUPMI, .nlpi_csi = NVME_CSI_NVM, .nlpi_vers = &nvme_vers_2v0, .nlpi_kind = NVME_LOG_ID_MANDATORY, .nlpi_source = NVME_LOG_DISC_S_SPEC, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len = sizeof (nvme_supmicmd_log_t) }, { .nlpi_short = "phyeye", .nlpi_human = "Physical Interface Receiver Eye Opening Measurement Log", .nlpi_lid = NVME_LOGPAGE_PHYEYE, .nlpi_csi = NVME_CSI_NVM, /* * This log page was introduced in the PCIe 1.1 supplement. There is no * good way to discover it. It has been implemented in NVMe 2.0 based * devices so we basically tag it as a 2.0 minimum version and then rely * on the 'suplog' log page to determine presence. */ .nlpi_vers = &nvme_vers_2v0, .nlpi_kind = NVME_LOG_ID_OPTIONAL, .nlpi_source = NVME_LOG_DISC_S_SPEC, .nlpi_disc = NVME_LOG_DISC_F_NEED_LSP | NVME_LOG_DISC_F_NEED_LSI, .nlpi_scope = NVME_LOG_SCOPE_CTRL, .nlpi_len = sizeof (nvme_eom_hdr_t), .nlpi_var_func = nvme_lpd_eom_len } }; const size_t nvme_std_log_npages = ARRAY_SIZE(nvme_std_log_pages); nvme_log_disc_scope_t nvme_log_page_info_scope(const nvme_log_page_info_t *info, const nvme_valid_ctrl_data_t *data) { if (info->nlpi_scope_func != NULL) { return (info->nlpi_scope_func(data, info)); } else { return (info->nlpi_scope); } } uint64_t nvme_log_page_info_size(const nvme_log_page_info_t *info, const nvme_valid_ctrl_data_t *data, bool *var) { uint64_t len; *var = info->nlpi_var_func != NULL; if (info->nlpi_len_func != NULL) { len = info->nlpi_len_func(data, info); } else { len = info->nlpi_len; } /* * Some vendor-specific log pages are not documented to have 4-byte * aligned lengths. This means that to get the full log page you must * round this up to ensure that you end up with a valid request. We opt * to do this here rather than have to check every single log page data * structure and fix it up manually. While it means consumers that are * using this to ignore information about the type itself may * erroneously display extra bytes (e.g. nvmeadm's default hex dumper), * that's better than getting an error or truncating the data. */ return (P2ROUNDUP(len, NVME_DWORD_SIZE)); } bool nvme_log_page_info_supported(const nvme_log_page_info_t *info, const nvme_valid_ctrl_data_t *data) { bool vers, sup_func; if (info->nlpi_vers != NULL) { vers = nvme_field_atleast(data, info->nlpi_vers); } else { vers = true; } if (info->nlpi_sup_func != NULL) { sup_func = info->nlpi_sup_func(data, info); } else { sup_func = true; } return (vers && sup_func); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Common field and validation for NVMe Namespace related commands. This covers * attaching and detaching controllers to namespaces as well as creating and * deleting namespaces. */ #include "nvme_common.h" #include static bool nvme_ns_attach_field_valid_sel(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t act, char *msg, size_t msglen) { const uint64_t min = NVME_NS_ATTACH_CTRL_ATTACH; const uint64_t max = NVME_NS_ATTACH_CTRL_DETACH; return (nvme_field_range_check(field, min, max, msg, msglen, act)); } const nvme_field_info_t nvme_ns_attach_fields[] = { [NVME_NS_ATTACH_REQ_FIELD_SEL] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_valid = nvme_ns_attach_field_valid_sel, .nlfi_spec = "sel", .nlfi_human = "select", .nlfi_def_req = true, .nlfi_def_allow = false }, [NVME_NS_ATTACH_REQ_FIELD_NSID] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_valid = nvme_field_valid_nsid, .nlfi_spec = "nsid", .nlfi_human = "namespace ID", .nlfi_def_req = true, .nlfi_def_allow = true }, /* * The interpretation of the data pointer is technically specific to the * select command. As we don't actually accept any data, right now this * is here just for libnvme tracking purposes. */ [NVME_NS_ATTACH_REQ_FIELD_DPTR] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_spec = "dptr", .nlfi_human = "data pointer", .nlfi_def_req = true, .nlfi_def_allow = true } }; const size_t nvme_ns_attach_nfields = ARRAY_SIZE(nvme_ns_attach_fields); const nvme_field_info_t nvme_ns_delete_fields[] = { [NVME_NS_DELETE_REQ_FIELD_NSID] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_valid = nvme_field_valid_nsid, .nlfi_spec = "nsid", .nlfi_human = "namespace ID", .nlfi_def_req = true, .nlfi_def_allow = true } }; const size_t nvme_ns_delete_nfields = ARRAY_SIZE(nvme_ns_delete_fields); /* * This is an integer which currently has only a single bit defined, bit 0. * Though NVMe 2.1 will change this. */ static bool nvme_ns_create_field_valid_nmic(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t act, char *msg, size_t msglen) { return (nvme_field_mask_check(field, NVME_NS_MGMT_NMIC_MASK, msg, msglen, act)); } /* * The NSZE and NCAP fields are related. Because these are in terms of a * formatted LBA which we don't have quite at this moment, we can't confirm the * maximum value; however, we do know enough to know a value of zero is wrong. */ static bool nvme_ns_attach_field_valid_nsze(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t act, char *msg, size_t msglen) { return (nvme_field_range_check(field, 1, UINT64_MAX, msg, msglen, act)); } /* * The set of fields that are required to be allowed or defaults varies based * upon the CSI that is used. For example the NVM and K/V command sets have * different required fields. As such, the notion of what's required and * settable is something that is determined by the CSI, hence why the required * and allowed fields are missing for everything that isn't the CSI. */ const nvme_field_info_t nvme_ns_create_fields[] = { [NVME_NS_CREATE_REQ_FIELD_CSI] = { /* * The libnvme APIs require that a CSI is passed to create this, * so this 2.0 isn't quite truly enforced. */ .nlfi_vers = &nvme_vers_2v0, .nlfi_max_size = NVME_NS_MGMT_MAX_CSI, .nlfi_spec = "csi", .nlfi_human = "command set ID", .nlfi_def_req = true, .nlfi_def_allow = false }, [NVME_NS_CREATE_REQ_FIELD_NSZE] = { .nlfi_vers = &nvme_vers_1v2, /* * The NSZE and NCAP fields are required to be related by a * namespace granularity record; however, this was not * introduced until NVMe 1.4. Absent this information, the main * constraint is that the two are equivalent unless thin * provisioning is supported. In addition, when setting the * field, we don't know what LBA size someone has selected, so * this can only be checked at command execution time. */ .nlfi_valid = nvme_ns_attach_field_valid_nsze, .nlfi_spec = "nsze", .nlfi_human = "namespace size" }, [NVME_NS_CREATE_REQ_FIELD_NCAP] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_valid = nvme_ns_attach_field_valid_nsze, .nlfi_spec = "ncap", .nlfi_human = "namespace capacity" }, [NVME_NS_CREATE_REQ_FIELD_FLBAS] = { .nlfi_vers = &nvme_vers_1v2, /* * See the notes in common/nvme/nvme_format.c around this choice * of maximum. */ .nlfi_max_size = NVME_NS_MGMT_MAX_FLBAS, .nlfi_spec = "flbas", .nlfi_human = "formatted LBA size" }, [NVME_NS_CREATE_REQ_FIELD_NMIC] = { .nlfi_vers = &nvme_vers_1v2, .nlfi_valid = nvme_ns_create_field_valid_nmic, .nlfi_max_size = NVME_NS_MGMT_MAX_FLBAS, .nlfi_spec = "nmic", .nlfi_human = "namespace multi-path I/O and namespace sharing " "capabilities" } }; const size_t nvme_ns_create_nfields = ARRAY_SIZE(nvme_ns_create_fields); /* * These are the default fields that are required for an NVM command. The CSI * cannot be set. The we allow to default to zero, unshared, and therefore is * optional. */ const nvme_ns_create_req_field_t nvme_ns_create_fields_nvm_req[] = { NVME_NS_CREATE_REQ_FIELD_NSZE, NVME_NS_CREATE_REQ_FIELD_NCAP, NVME_NS_CREATE_REQ_FIELD_FLBAS }; const size_t nvme_ns_create_fields_nvm_nreq = ARRAY_SIZE(nvme_ns_create_fields_nvm_req); const nvme_ns_create_req_field_t nvme_ns_create_fields_nvm_allow[] = { NVME_NS_CREATE_REQ_FIELD_NSZE, NVME_NS_CREATE_REQ_FIELD_NCAP, NVME_NS_CREATE_REQ_FIELD_FLBAS, NVME_NS_CREATE_REQ_FIELD_NMIC }; const size_t nvme_ns_create_fields_nvm_nallow = ARRAY_SIZE(nvme_ns_create_fields_nvm_allow); bool nvme_nsmgmt_cmds_supported(const nvme_valid_ctrl_data_t *data) { if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v2)) { return (data->vcd_id->id_oacs.oa_nsmgmt != 0); } return (false); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Common definitions and values for NVMe version use. */ #include "nvme_common.h" const nvme_version_t nvme_vers_1v0 = { .v_major = 1, .v_minor = 0 }; const nvme_version_t nvme_vers_1v1 = { .v_major = 1, .v_minor = 1 }; const nvme_version_t nvme_vers_1v2 = { .v_major = 1, .v_minor = 2 }; const nvme_version_t nvme_vers_1v3 = { .v_major = 1, .v_minor = 3 }; const nvme_version_t nvme_vers_1v4 = { .v_major = 1, .v_minor = 4 }; const nvme_version_t nvme_vers_2v0 = { .v_major = 2, .v_minor = 0 }; const nvme_version_t nvme_vers_2v1 = { .v_major = 2, .v_minor = 1 }; bool nvme_vers_atleast(const nvme_version_t *dev, const nvme_version_t *targ) { return (dev->v_major > targ->v_major || (dev->v_major == targ->v_major && dev->v_minor >= targ->v_minor)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Common field and validation pieces for NVMe Vendor Unique Admin and NVM * commands. */ #include "nvme_common.h" #include #ifdef _KERNEL #include #include #else #include #include #endif /* * Right now this mainly checks for a valid admin command set operation code. * When we end up supporting commands that are meant to be submitted to NVM * queues, we'll likely need to add a way to know what type of command set we're * targeting. */ static bool nvme_vuc_field_valid_opc(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t opcode, char *msg, size_t msglen) { return (nvme_field_range_check(field, NVME_PASSTHRU_MIN_ADMIN_OPC, NVME_PASSTHRU_MAX_ADMIN_OPC, msg, msglen, opcode)); } /* * Unlike with log pages, identify commands, features, etc. we do not know how * the namespace will be used. It may be zero, it may need to be the broadcast * namespace, or it may target a device specific namespace. As such, we accept * all of these, which isn't normally the case. */ static bool nvme_vuc_field_valid_nsid(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t nsid, char *msg, size_t msglen) { if (nsid == 0) { return (true); } return (nvme_field_valid_nsid(field, data, nsid, msg, msglen)); } /* * A VUC data length is in dwords. It's our responsibility to make sure that * this is properly aligned. Though zero is a valid value. */ static bool nvme_vuc_field_valid_ndt(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t len, char *msg, size_t msglen) { uint64_t max = (uint64_t)UINT32_MAX << NVME_DWORD_SHIFT; if ((len % NVME_DWORD_SIZE) != 0) { (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is " "invalid: value must be %u-byte aligned", field->nlfi_human, field->nlfi_spec, len, NVME_DWORD_SIZE); return (false); } return (nvme_field_range_check(field, 0, max, msg, msglen, len)); } /* * The maximum timeout is controlled by the kernel. The only real constraint * right now is that it fit into the ioctl size and is non-zero. */ static bool nvme_vuc_field_valid_to(const nvme_field_info_t *field, const nvme_valid_ctrl_data_t *data, uint64_t to, char *msg, size_t msglen) { return (nvme_field_range_check(field, 1, UINT32_MAX, msg, msglen, to)); } const nvme_field_info_t nvme_vuc_fields[] = { [NVME_VUC_REQ_FIELD_OPC] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_vuc_field_valid_opc, .nlfi_spec = "opc", .nlfi_human = "opcode", .nlfi_def_req = true, .nlfi_def_allow = true }, [NVME_VUC_REQ_FIELD_NSID] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_vuc_field_valid_nsid, .nlfi_spec = "nsid", .nlfi_human = "namespace ID", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_VUC_REQ_FIELD_CDW12] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = UINT32_MAX, .nlfi_spec = "cdw12", .nlfi_human = "command dword 12", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_VUC_REQ_FIELD_CDW13] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = UINT32_MAX, .nlfi_spec = "cdw13", .nlfi_human = "command dword 13", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_VUC_REQ_FIELD_CDW14] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = UINT32_MAX, .nlfi_spec = "cdw14", .nlfi_human = "command dword 14", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_VUC_REQ_FIELD_CDW15] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_max_size = UINT32_MAX, .nlfi_spec = "cdw15", .nlfi_human = "command dword 15", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_VUC_REQ_FIELD_NDT] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_vuc_field_valid_ndt, .nlfi_spec = "ndt", .nlfi_human = "number of dwords in data transfer", .nlfi_def_req = false, .nlfi_def_allow = true }, [NVME_VUC_REQ_FIELD_TO] = { .nlfi_vers = &nvme_vers_1v0, .nlfi_valid = nvme_vuc_field_valid_to, .nlfi_spec = "to", .nlfi_human = "timeout", .nlfi_def_req = true, .nlfi_def_allow = true } }; const size_t nvme_vuc_nfields = ARRAY_SIZE(nvme_vuc_fields);