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
nvme_fw_cmds_supported(const nvme_valid_ctrl_data_t * data)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
nvme_fw_load_granularity(const nvme_valid_ctrl_data_t * data)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
nvme_fw_load_field_valid_common(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,uint64_t len,bool zero_ok,char * msg,size_t msglen)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
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)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
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)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
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)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
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)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