xref: /illumos-gate/usr/src/common/nvme/nvme_firmware.c (revision f40f2f085cf8ecca831ce8866e47dcbe39fa3e7c)
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