xref: /illumos-gate/usr/src/common/nvme/nvme_firmware.c (revision 29afde7ed707ee1c13d8307aaf2bfa026d54d66d)
1533affcbSRobert Mustacchi /*
2533affcbSRobert Mustacchi  * This file and its contents are supplied under the terms of the
3533affcbSRobert Mustacchi  * Common Development and Distribution License ("CDDL"), version 1.0.
4533affcbSRobert Mustacchi  * You may only use this file in accordance with the terms of version
5533affcbSRobert Mustacchi  * 1.0 of the CDDL.
6533affcbSRobert Mustacchi  *
7533affcbSRobert Mustacchi  * A full copy of the text of the CDDL should have accompanied this
8533affcbSRobert Mustacchi  * source.  A copy of the CDDL is also available via the Internet at
9533affcbSRobert Mustacchi  * http://www.illumos.org/license/CDDL.
10533affcbSRobert Mustacchi  */
11533affcbSRobert Mustacchi 
12533affcbSRobert Mustacchi /*
13533affcbSRobert Mustacchi  * Copyright 2024 Oxide Computer Company
14533affcbSRobert Mustacchi  */
15533affcbSRobert Mustacchi 
16533affcbSRobert Mustacchi /*
17533affcbSRobert Mustacchi  * Common field and validation for NVMe firmware related pieces.
18533affcbSRobert Mustacchi  */
19533affcbSRobert Mustacchi 
20533affcbSRobert Mustacchi #include "nvme_common.h"
21533affcbSRobert Mustacchi 
22533affcbSRobert Mustacchi #include <sys/sysmacros.h>
23533affcbSRobert Mustacchi #ifdef	_KERNEL
24533affcbSRobert Mustacchi #include <sys/sunddi.h>
25533affcbSRobert Mustacchi #include <sys/stdint.h>
26533affcbSRobert Mustacchi #else
27533affcbSRobert Mustacchi #include <stdio.h>
28533affcbSRobert Mustacchi #include <inttypes.h>
29533affcbSRobert Mustacchi #endif
30533affcbSRobert Mustacchi 
31533affcbSRobert Mustacchi /*
32533affcbSRobert Mustacchi  * The default granularity we enforce prior to the 1.3 spec's introduction of
33533affcbSRobert Mustacchi  * the FWUG (firmware update granularity).
34533affcbSRobert Mustacchi  */
35533affcbSRobert Mustacchi #define	NVME_DEFAULT_FWUG	4096
36533affcbSRobert Mustacchi 
37533affcbSRobert Mustacchi /*
38533affcbSRobert Mustacchi  * The FWUG is in multiples of 4 KiB.
39533affcbSRobert Mustacchi  */
40533affcbSRobert Mustacchi #define	NVME_FWUG_MULT	4096
41533affcbSRobert Mustacchi 
42533affcbSRobert Mustacchi /*
43533affcbSRobert Mustacchi  * Answers the question of are firmware commands supported or not in a way
44533affcbSRobert Mustacchi  * that is a bit easier for us to unit test.
45533affcbSRobert Mustacchi  */
46533affcbSRobert Mustacchi bool
nvme_fw_cmds_supported(const nvme_valid_ctrl_data_t * data)47533affcbSRobert Mustacchi nvme_fw_cmds_supported(const nvme_valid_ctrl_data_t *data)
48533affcbSRobert Mustacchi {
49533affcbSRobert Mustacchi 	return (data->vcd_id->id_oacs.oa_firmware != 0);
50533affcbSRobert Mustacchi }
51533affcbSRobert Mustacchi 
52533affcbSRobert Mustacchi /*
53*29afde7eSAndy Fiddaman  * Validate a length/offset for an NVMe firmware download request.
54*29afde7eSAndy Fiddaman  * These fields in the NVMe specification are in units of uint32_t values but,
55*29afde7eSAndy Fiddaman  * since the ioctl interfaces deal with byte counts, so do these validation
56*29afde7eSAndy Fiddaman  * functions. According to the specification the same constraints hold for
57*29afde7eSAndy Fiddaman  * both the length and offset fields; experience has shown, however, that we
58*29afde7eSAndy Fiddaman  * need to be more relaxed when validating the length -- see the comment in the
59*29afde7eSAndy Fiddaman  * validation function below.
60*29afde7eSAndy Fiddaman  *
61*29afde7eSAndy Fiddaman  * Starting in NVMe 1.3, additional constraints about the granularity were
62*29afde7eSAndy Fiddaman  * added through the FWUG field in the identify controller data structure. This
63*29afde7eSAndy Fiddaman  * indicates the required alignment in 4 KiB chunks. The controller is allowed
64*29afde7eSAndy Fiddaman  * to indicate a value of 0 to indicate that this is unknown (which is not
65*29afde7eSAndy Fiddaman  * particularly helpful) or that it may be 0xff which indicates that there is
66*29afde7eSAndy Fiddaman  * no alignment constraint other than the natural uint32_t alignment.
67533affcbSRobert Mustacchi  *
68533affcbSRobert Mustacchi  * For devices that exist prior to NVMe 1.3, we assume that we probably need at
69533affcbSRobert Mustacchi  * least 4 KiB granularity for the time being. This may need to change in the
70533affcbSRobert Mustacchi  * future.
71533affcbSRobert Mustacchi  */
72533affcbSRobert Mustacchi uint32_t
nvme_fw_load_granularity(const nvme_valid_ctrl_data_t * data)73533affcbSRobert Mustacchi nvme_fw_load_granularity(const nvme_valid_ctrl_data_t *data)
74533affcbSRobert Mustacchi {
75533affcbSRobert Mustacchi 	uint32_t gran = NVME_DEFAULT_FWUG;
76533affcbSRobert Mustacchi 
77533affcbSRobert Mustacchi 	if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) {
78533affcbSRobert Mustacchi 		const uint8_t fwug = data->vcd_id->ap_fwug;
79533affcbSRobert Mustacchi 		if (fwug == 0xff) {
80*29afde7eSAndy Fiddaman 			gran = NVME_DWORD_SIZE;
81533affcbSRobert Mustacchi 		} else if (fwug != 0) {
82533affcbSRobert Mustacchi 			gran = fwug * NVME_FWUG_MULT;
83533affcbSRobert Mustacchi 		}
84533affcbSRobert Mustacchi 	}
85533affcbSRobert Mustacchi 
86533affcbSRobert Mustacchi 	return (gran);
87533affcbSRobert Mustacchi }
88533affcbSRobert Mustacchi 
89533affcbSRobert Mustacchi 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)90533affcbSRobert Mustacchi nvme_fw_load_field_valid_len(const nvme_field_info_t *field,
91533affcbSRobert Mustacchi     const nvme_valid_ctrl_data_t *data, uint64_t len, char *msg, size_t msglen)
92533affcbSRobert Mustacchi {
93*29afde7eSAndy Fiddaman 	/*
94*29afde7eSAndy Fiddaman 	 * While we would like to validate that the length is consistent with
95*29afde7eSAndy Fiddaman 	 * the firmware upgrade granularity, we have encountered drives where
96*29afde7eSAndy Fiddaman 	 * the vendor's firmware update file sizes are not a multiple of the
97*29afde7eSAndy Fiddaman 	 * required granularity, and where the strategy of padding the last
98*29afde7eSAndy Fiddaman 	 * block out to that required granularity does not always result in a
99*29afde7eSAndy Fiddaman 	 * file that the drive will accept.
100*29afde7eSAndy Fiddaman 	 *
101*29afde7eSAndy Fiddaman 	 * The best we can do is ensure that it is a whole number of dwords.
102*29afde7eSAndy Fiddaman 	 */
103*29afde7eSAndy Fiddaman 	if ((len & NVME_DWORD_MASK) != 0) {
104*29afde7eSAndy Fiddaman 		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must "
105*29afde7eSAndy Fiddaman 		    "be aligned to the firmware update granularity 0x%x",
106*29afde7eSAndy Fiddaman 		    field->nlfi_human, field->nlfi_spec, len, NVME_DWORD_SIZE);
107*29afde7eSAndy Fiddaman 		return (false);
108*29afde7eSAndy Fiddaman 	}
109*29afde7eSAndy Fiddaman 	return (nvme_field_range_check(field, NVME_DWORD_SIZE,
110*29afde7eSAndy Fiddaman 	    NVME_FW_LENB_MAX, msg, msglen, len));
111533affcbSRobert Mustacchi }
112533affcbSRobert Mustacchi 
113533affcbSRobert Mustacchi 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)114533affcbSRobert Mustacchi nvme_fw_load_field_valid_offset(const nvme_field_info_t *field,
115533affcbSRobert Mustacchi     const nvme_valid_ctrl_data_t *data, uint64_t off, char *msg, size_t msglen)
116533affcbSRobert Mustacchi {
117*29afde7eSAndy Fiddaman 	uint32_t gran = nvme_fw_load_granularity(data);
118*29afde7eSAndy Fiddaman 
119*29afde7eSAndy Fiddaman 	if ((off % gran) != 0) {
120*29afde7eSAndy Fiddaman 		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must "
121*29afde7eSAndy Fiddaman 		    "be aligned to the firmware update granularity 0x%x",
122*29afde7eSAndy Fiddaman 		    field->nlfi_human, field->nlfi_spec, off, gran);
123*29afde7eSAndy Fiddaman 		return (false);
124*29afde7eSAndy Fiddaman 	}
125*29afde7eSAndy Fiddaman 
126*29afde7eSAndy Fiddaman 	return (nvme_field_range_check(field, 0, NVME_FW_OFFSETB_MAX, msg,
127*29afde7eSAndy Fiddaman 	    msglen, off));
128533affcbSRobert Mustacchi }
129533affcbSRobert Mustacchi 
130533affcbSRobert Mustacchi const nvme_field_info_t nvme_fw_load_fields[] = {
131533affcbSRobert Mustacchi 	[NVME_FW_LOAD_REQ_FIELD_NUMD] = {
132533affcbSRobert Mustacchi 		.nlfi_vers = &nvme_vers_1v0,
133533affcbSRobert Mustacchi 		.nlfi_valid = nvme_fw_load_field_valid_len,
134533affcbSRobert Mustacchi 		.nlfi_spec = "numd",
135533affcbSRobert Mustacchi 		.nlfi_human = "number of dwords",
136533affcbSRobert Mustacchi 		.nlfi_def_req = true,
137533affcbSRobert Mustacchi 		.nlfi_def_allow = true
138533affcbSRobert Mustacchi 	},
139533affcbSRobert Mustacchi 	[NVME_FW_LOAD_REQ_FIELD_OFFSET] = {
140533affcbSRobert Mustacchi 		.nlfi_vers = &nvme_vers_1v0,
141533affcbSRobert Mustacchi 		.nlfi_valid = nvme_fw_load_field_valid_offset,
142533affcbSRobert Mustacchi 		.nlfi_spec = "ofst",
143533affcbSRobert Mustacchi 		.nlfi_human = "offset",
144533affcbSRobert Mustacchi 		.nlfi_def_req = true,
145533affcbSRobert Mustacchi 		.nlfi_def_allow = true
146533affcbSRobert Mustacchi 	}
147533affcbSRobert Mustacchi };
148533affcbSRobert Mustacchi 
149533affcbSRobert Mustacchi size_t nvme_fw_load_nfields = ARRAY_SIZE(nvme_fw_load_fields);
150533affcbSRobert Mustacchi 
151533affcbSRobert Mustacchi 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)152533affcbSRobert Mustacchi nvme_fw_commit_field_valid_slot(const nvme_field_info_t *field,
153533affcbSRobert Mustacchi     const nvme_valid_ctrl_data_t *data, uint64_t slot, char *msg, size_t msglen)
154533affcbSRobert Mustacchi {
155533affcbSRobert Mustacchi 	return (nvme_field_range_check(field, NVME_FW_SLOT_MIN,
156533affcbSRobert Mustacchi 	    data->vcd_id->id_frmw.fw_nslot, msg, msglen, slot));
157533affcbSRobert Mustacchi }
158533affcbSRobert Mustacchi 
159533affcbSRobert Mustacchi /*
160533affcbSRobert Mustacchi  * This validation function represents an area of improvement that we'd like to
161533affcbSRobert Mustacchi  * figure out in the future. Immediate firmware activations are only supported
162533affcbSRobert Mustacchi  * in NVMe 1.3, so while it's a bad value prior to NVMe 1.3, that is a somewhat
163533affcbSRobert Mustacchi  * confusing error. In addition, the various boot partition updates are not
164533affcbSRobert Mustacchi  * supported, so it's not a bad value to the spec, but just to us.
165533affcbSRobert Mustacchi  */
166533affcbSRobert Mustacchi 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)167533affcbSRobert Mustacchi nvme_fw_commit_field_valid_act(const nvme_field_info_t *field,
168533affcbSRobert Mustacchi     const nvme_valid_ctrl_data_t *data, uint64_t act, char *msg, size_t msglen)
169533affcbSRobert Mustacchi {
170533affcbSRobert Mustacchi 	uint64_t max = NVME_FWC_ACTIVATE;
171533affcbSRobert Mustacchi 
172533affcbSRobert Mustacchi 	if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) {
173533affcbSRobert Mustacchi 		max = NVME_FWC_ACTIVATE_IMMED;
174533affcbSRobert Mustacchi 	}
175533affcbSRobert Mustacchi 
176533affcbSRobert Mustacchi 	return (nvme_field_range_check(field, 0, max, msg, msglen, act));
177533affcbSRobert Mustacchi }
178533affcbSRobert Mustacchi 
179533affcbSRobert Mustacchi const nvme_field_info_t nvme_fw_commit_fields[] = {
180533affcbSRobert Mustacchi 	[NVME_FW_COMMIT_REQ_FIELD_SLOT] = {
181533affcbSRobert Mustacchi 		.nlfi_vers = &nvme_vers_1v0,
182533affcbSRobert Mustacchi 		.nlfi_valid = nvme_fw_commit_field_valid_slot,
183533affcbSRobert Mustacchi 		.nlfi_spec = "fs",
184533affcbSRobert Mustacchi 		.nlfi_human = "firmware slot",
185533affcbSRobert Mustacchi 		.nlfi_def_req = true,
186533affcbSRobert Mustacchi 		.nlfi_def_allow = true
187533affcbSRobert Mustacchi 	},
188533affcbSRobert Mustacchi 	[NVME_FW_COMMIT_REQ_FIELD_ACT] = {
189533affcbSRobert Mustacchi 		.nlfi_vers = &nvme_vers_1v0,
190533affcbSRobert Mustacchi 		.nlfi_valid = nvme_fw_commit_field_valid_act,
191533affcbSRobert Mustacchi 		.nlfi_spec = "ca",
192533affcbSRobert Mustacchi 		.nlfi_human = "commit action",
193533affcbSRobert Mustacchi 		.nlfi_def_req = true,
194533affcbSRobert Mustacchi 		.nlfi_def_allow = true
195533affcbSRobert Mustacchi 	}
196533affcbSRobert Mustacchi };
197533affcbSRobert Mustacchi 
198533affcbSRobert Mustacchi size_t nvme_fw_commit_nfields = ARRAY_SIZE(nvme_fw_commit_fields);
199