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 * Common field and validation for NVMe firmware related pieces. 18 */ 19 20 #include "nvme_common.h" 21 22 #include <sys/sysmacros.h> 23 #ifdef _KERNEL 24 #include <sys/sunddi.h> 25 #include <sys/stdint.h> 26 #else 27 #include <stdio.h> 28 #include <inttypes.h> 29 #endif 30 31 /* 32 * The default granularity we enforce prior to the 1.3 spec's introduction of 33 * the FWUG (firmware update granularity). 34 */ 35 #define NVME_DEFAULT_FWUG 4096 36 37 /* 38 * The FWUG is in multiples of 4 KiB. 39 */ 40 #define NVME_FWUG_MULT 4096 41 42 /* 43 * Answers the question of are firmware commands supported or not in a way 44 * that is a bit easier for us to unit test. 45 */ 46 bool 47 nvme_fw_cmds_supported(const nvme_valid_ctrl_data_t *data) 48 { 49 return (data->vcd_id->id_oacs.oa_firmware != 0); 50 } 51 52 /* 53 * Validate a length for an NVMe firmware download request. The same constraints 54 * hold for both the length and offset fields. These fields are in units of 55 * uint32_t values. Starting in NVMe 1.3, additional constraints about the 56 * granularity were added through the FWUG field in the identify controller data 57 * structure. This indicates the required alignment in 4 KiB chunks. The 58 * controller is allowed to indicate a value of 0 to indicate that this is 59 * unknown (which is not particularly helpful) or that it may be 0xff which 60 * indicates that there is no alignment constraint other than the natural 61 * uint32_t alignment. 62 * 63 * For devices that exist prior to NVMe 1.3, we assume that we probably need at 64 * least 4 KiB granularity for the time being. This may need to change in the 65 * future. 66 */ 67 uint32_t 68 nvme_fw_load_granularity(const nvme_valid_ctrl_data_t *data) 69 { 70 uint32_t gran = NVME_DEFAULT_FWUG; 71 72 if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) { 73 const uint8_t fwug = data->vcd_id->ap_fwug; 74 if (fwug == 0xff) { 75 gran = 4; 76 } else if (fwug != 0) { 77 gran = fwug * NVME_FWUG_MULT; 78 } 79 } 80 81 return (gran); 82 } 83 84 static bool 85 nvme_fw_load_field_valid_common(const nvme_field_info_t *field, 86 const nvme_valid_ctrl_data_t *data, uint64_t len, bool zero_ok, char *msg, 87 size_t msglen) 88 { 89 uint32_t gran = nvme_fw_load_granularity(data); 90 uint64_t min = zero_ok ? 0 : 1; 91 92 if ((len % gran) != 0) { 93 (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must " 94 "be aligned to the firmware update granularity 0x%x", 95 field->nlfi_human, field->nlfi_spec, len, gran); 96 return (false); 97 } 98 99 return (nvme_field_range_check(field, min, NVME_FW_OFFSETB_MAX, msg, 100 msglen, len)); 101 } 102 103 static bool 104 nvme_fw_load_field_valid_len(const nvme_field_info_t *field, 105 const nvme_valid_ctrl_data_t *data, uint64_t len, char *msg, size_t msglen) 106 { 107 return (nvme_fw_load_field_valid_common(field, data, len, false, msg, 108 msglen)); 109 } 110 111 static bool 112 nvme_fw_load_field_valid_offset(const nvme_field_info_t *field, 113 const nvme_valid_ctrl_data_t *data, uint64_t off, char *msg, size_t msglen) 114 { 115 return (nvme_fw_load_field_valid_common(field, data, off, true, msg, 116 msglen)); 117 } 118 119 const nvme_field_info_t nvme_fw_load_fields[] = { 120 [NVME_FW_LOAD_REQ_FIELD_NUMD] = { 121 .nlfi_vers = &nvme_vers_1v0, 122 .nlfi_valid = nvme_fw_load_field_valid_len, 123 .nlfi_spec = "numd", 124 .nlfi_human = "number of dwords", 125 .nlfi_def_req = true, 126 .nlfi_def_allow = true 127 }, 128 [NVME_FW_LOAD_REQ_FIELD_OFFSET] = { 129 .nlfi_vers = &nvme_vers_1v0, 130 .nlfi_valid = nvme_fw_load_field_valid_offset, 131 .nlfi_spec = "ofst", 132 .nlfi_human = "offset", 133 .nlfi_def_req = true, 134 .nlfi_def_allow = true 135 } 136 }; 137 138 size_t nvme_fw_load_nfields = ARRAY_SIZE(nvme_fw_load_fields); 139 140 static bool 141 nvme_fw_commit_field_valid_slot(const nvme_field_info_t *field, 142 const nvme_valid_ctrl_data_t *data, uint64_t slot, char *msg, size_t msglen) 143 { 144 return (nvme_field_range_check(field, NVME_FW_SLOT_MIN, 145 data->vcd_id->id_frmw.fw_nslot, msg, msglen, slot)); 146 } 147 148 /* 149 * This validation function represents an area of improvement that we'd like to 150 * figure out in the future. Immediate firmware activations are only supported 151 * in NVMe 1.3, so while it's a bad value prior to NVMe 1.3, that is a somewhat 152 * confusing error. In addition, the various boot partition updates are not 153 * supported, so it's not a bad value to the spec, but just to us. 154 */ 155 static bool 156 nvme_fw_commit_field_valid_act(const nvme_field_info_t *field, 157 const nvme_valid_ctrl_data_t *data, uint64_t act, char *msg, size_t msglen) 158 { 159 uint64_t max = NVME_FWC_ACTIVATE; 160 161 if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) { 162 max = NVME_FWC_ACTIVATE_IMMED; 163 } 164 165 return (nvme_field_range_check(field, 0, max, msg, msglen, act)); 166 } 167 168 const nvme_field_info_t nvme_fw_commit_fields[] = { 169 [NVME_FW_COMMIT_REQ_FIELD_SLOT] = { 170 .nlfi_vers = &nvme_vers_1v0, 171 .nlfi_valid = nvme_fw_commit_field_valid_slot, 172 .nlfi_spec = "fs", 173 .nlfi_human = "firmware slot", 174 .nlfi_def_req = true, 175 .nlfi_def_allow = true 176 }, 177 [NVME_FW_COMMIT_REQ_FIELD_ACT] = { 178 .nlfi_vers = &nvme_vers_1v0, 179 .nlfi_valid = nvme_fw_commit_field_valid_act, 180 .nlfi_spec = "ca", 181 .nlfi_human = "commit action", 182 .nlfi_def_req = true, 183 .nlfi_def_allow = true 184 } 185 }; 186 187 size_t nvme_fw_commit_nfields = ARRAY_SIZE(nvme_fw_commit_fields); 188