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/offset for an NVMe firmware download request. 54 * These fields in the NVMe specification are in units of uint32_t values but, 55 * since the ioctl interfaces deal with byte counts, so do these validation 56 * functions. According to the specification the same constraints hold for 57 * both the length and offset fields; experience has shown, however, that we 58 * need to be more relaxed when validating the length -- see the comment in the 59 * validation function below. 60 * 61 * Starting in NVMe 1.3, additional constraints about the granularity were 62 * added through the FWUG field in the identify controller data structure. This 63 * indicates the required alignment in 4 KiB chunks. The controller is allowed 64 * to indicate a value of 0 to indicate that this is unknown (which is not 65 * particularly helpful) or that it may be 0xff which indicates that there is 66 * no alignment constraint other than the natural uint32_t alignment. 67 * 68 * For devices that exist prior to NVMe 1.3, we assume that we probably need at 69 * least 4 KiB granularity for the time being. This may need to change in the 70 * future. 71 */ 72 uint32_t 73 nvme_fw_load_granularity(const nvme_valid_ctrl_data_t *data) 74 { 75 uint32_t gran = NVME_DEFAULT_FWUG; 76 77 if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) { 78 const uint8_t fwug = data->vcd_id->ap_fwug; 79 if (fwug == 0xff) { 80 gran = NVME_DWORD_SIZE; 81 } else if (fwug != 0) { 82 gran = fwug * NVME_FWUG_MULT; 83 } 84 } 85 86 return (gran); 87 } 88 89 static bool 90 nvme_fw_load_field_valid_len(const nvme_field_info_t *field, 91 const nvme_valid_ctrl_data_t *data, uint64_t len, char *msg, size_t msglen) 92 { 93 /* 94 * While we would like to validate that the length is consistent with 95 * the firmware upgrade granularity, we have encountered drives where 96 * the vendor's firmware update file sizes are not a multiple of the 97 * required granularity, and where the strategy of padding the last 98 * block out to that required granularity does not always result in a 99 * file that the drive will accept. 100 * 101 * The best we can do is ensure that it is a whole number of dwords. 102 */ 103 if ((len & NVME_DWORD_MASK) != 0) { 104 (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must " 105 "be aligned to the firmware update granularity 0x%x", 106 field->nlfi_human, field->nlfi_spec, len, NVME_DWORD_SIZE); 107 return (false); 108 } 109 return (nvme_field_range_check(field, NVME_DWORD_SIZE, 110 NVME_FW_LENB_MAX, msg, msglen, len)); 111 } 112 113 static bool 114 nvme_fw_load_field_valid_offset(const nvme_field_info_t *field, 115 const nvme_valid_ctrl_data_t *data, uint64_t off, char *msg, size_t msglen) 116 { 117 uint32_t gran = nvme_fw_load_granularity(data); 118 119 if ((off % gran) != 0) { 120 (void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must " 121 "be aligned to the firmware update granularity 0x%x", 122 field->nlfi_human, field->nlfi_spec, off, gran); 123 return (false); 124 } 125 126 return (nvme_field_range_check(field, 0, NVME_FW_OFFSETB_MAX, msg, 127 msglen, off)); 128 } 129 130 const nvme_field_info_t nvme_fw_load_fields[] = { 131 [NVME_FW_LOAD_REQ_FIELD_NUMD] = { 132 .nlfi_vers = &nvme_vers_1v0, 133 .nlfi_valid = nvme_fw_load_field_valid_len, 134 .nlfi_spec = "numd", 135 .nlfi_human = "number of dwords", 136 .nlfi_def_req = true, 137 .nlfi_def_allow = true 138 }, 139 [NVME_FW_LOAD_REQ_FIELD_OFFSET] = { 140 .nlfi_vers = &nvme_vers_1v0, 141 .nlfi_valid = nvme_fw_load_field_valid_offset, 142 .nlfi_spec = "ofst", 143 .nlfi_human = "offset", 144 .nlfi_def_req = true, 145 .nlfi_def_allow = true 146 } 147 }; 148 149 size_t nvme_fw_load_nfields = ARRAY_SIZE(nvme_fw_load_fields); 150 151 static bool 152 nvme_fw_commit_field_valid_slot(const nvme_field_info_t *field, 153 const nvme_valid_ctrl_data_t *data, uint64_t slot, char *msg, size_t msglen) 154 { 155 return (nvme_field_range_check(field, NVME_FW_SLOT_MIN, 156 data->vcd_id->id_frmw.fw_nslot, msg, msglen, slot)); 157 } 158 159 /* 160 * This validation function represents an area of improvement that we'd like to 161 * figure out in the future. Immediate firmware activations are only supported 162 * in NVMe 1.3, so while it's a bad value prior to NVMe 1.3, that is a somewhat 163 * confusing error. In addition, the various boot partition updates are not 164 * supported, so it's not a bad value to the spec, but just to us. 165 */ 166 static bool 167 nvme_fw_commit_field_valid_act(const nvme_field_info_t *field, 168 const nvme_valid_ctrl_data_t *data, uint64_t act, char *msg, size_t msglen) 169 { 170 uint64_t max = NVME_FWC_ACTIVATE; 171 172 if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) { 173 max = NVME_FWC_ACTIVATE_IMMED; 174 } 175 176 return (nvme_field_range_check(field, 0, max, msg, msglen, act)); 177 } 178 179 const nvme_field_info_t nvme_fw_commit_fields[] = { 180 [NVME_FW_COMMIT_REQ_FIELD_SLOT] = { 181 .nlfi_vers = &nvme_vers_1v0, 182 .nlfi_valid = nvme_fw_commit_field_valid_slot, 183 .nlfi_spec = "fs", 184 .nlfi_human = "firmware slot", 185 .nlfi_def_req = true, 186 .nlfi_def_allow = true 187 }, 188 [NVME_FW_COMMIT_REQ_FIELD_ACT] = { 189 .nlfi_vers = &nvme_vers_1v0, 190 .nlfi_valid = nvme_fw_commit_field_valid_act, 191 .nlfi_spec = "ca", 192 .nlfi_human = "commit action", 193 .nlfi_def_req = true, 194 .nlfi_def_allow = true 195 } 196 }; 197 198 size_t nvme_fw_commit_nfields = ARRAY_SIZE(nvme_fw_commit_fields); 199