|
root / base / usr / src / common / nvme / nvme_firmware.c
nvme_firmware.c C 199 lines 6.2 KB
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2025 Oxide Computer Company
 */

/*
 * Common field and validation for NVMe firmware related pieces.
 */

#include "nvme_common.h"

#include <sys/sysmacros.h>
#ifdef	_KERNEL
#include <sys/sunddi.h>
#include <sys/stdint.h>
#else
#include <stdio.h>
#include <inttypes.h>
#endif

/*
 * The default granularity we enforce prior to the 1.3 spec's introduction of
 * the FWUG (firmware update granularity).
 */
#define	NVME_DEFAULT_FWUG	4096

/*
 * The FWUG is in multiples of 4 KiB.
 */
#define	NVME_FWUG_MULT	4096

/*
 * Answers the question of are firmware commands supported or not in a way
 * that is a bit easier for us to unit test.
 */
bool
nvme_fw_cmds_supported(const nvme_valid_ctrl_data_t *data)
{
	return (data->vcd_id->id_oacs.oa_firmware != 0);
}

/*
 * Validate a length/offset for an NVMe firmware download request.
 * These fields in the NVMe specification are in units of uint32_t values but,
 * since the ioctl interfaces deal with byte counts, so do these validation
 * functions. According to the specification the same constraints hold for
 * both the length and offset fields; experience has shown, however, that we
 * need to be more relaxed when validating the length -- see the comment in the
 * validation function below.
 *
 * Starting in NVMe 1.3, additional constraints about the granularity were
 * added through the FWUG field in the identify controller data structure. This
 * indicates the required alignment in 4 KiB chunks. The controller is allowed
 * to indicate a value of 0 to indicate that this is unknown (which is not
 * particularly helpful) or that it may be 0xff which indicates that there is
 * no alignment constraint other than the natural uint32_t alignment.
 *
 * For devices that exist prior to NVMe 1.3, we assume that we probably need at
 * least 4 KiB granularity for the time being. This may need to change in the
 * future.
 */
uint32_t
nvme_fw_load_granularity(const nvme_valid_ctrl_data_t *data)
{
	uint32_t gran = NVME_DEFAULT_FWUG;

	if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) {
		const uint8_t fwug = data->vcd_id->ap_fwug;
		if (fwug == 0xff) {
			gran = NVME_DWORD_SIZE;
		} else if (fwug != 0) {
			gran = fwug * NVME_FWUG_MULT;
		}
	}

	return (gran);
}

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)
{
	/*
	 * While we would like to validate that the length is consistent with
	 * the firmware upgrade granularity, we have encountered drives where
	 * the vendor's firmware update file sizes are not a multiple of the
	 * required granularity, and where the strategy of padding the last
	 * block out to that required granularity does not always result in a
	 * file that the drive will accept.
	 *
	 * The best we can do is ensure that it is a whole number of dwords.
	 */
	if ((len & NVME_DWORD_MASK) != 0) {
		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must "
		    "be aligned to the firmware update granularity 0x%x",
		    field->nlfi_human, field->nlfi_spec, len, NVME_DWORD_SIZE);
		return (false);
	}
	return (nvme_field_range_check(field, NVME_DWORD_SIZE,
	    NVME_FW_LENB_MAX, msg, msglen, len));
}

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)
{
	uint32_t gran = nvme_fw_load_granularity(data);

	if ((off % gran) != 0) {
		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " must "
		    "be aligned to the firmware update granularity 0x%x",
		    field->nlfi_human, field->nlfi_spec, off, gran);
		return (false);
	}

	return (nvme_field_range_check(field, 0, NVME_FW_OFFSETB_MAX, msg,
	    msglen, off));
}

const nvme_field_info_t nvme_fw_load_fields[] = {
	[NVME_FW_LOAD_REQ_FIELD_NUMD] = {
		.nlfi_vers = &nvme_vers_1v0,
		.nlfi_valid = nvme_fw_load_field_valid_len,
		.nlfi_spec = "numd",
		.nlfi_human = "number of dwords",
		.nlfi_def_req = true,
		.nlfi_def_allow = true
	},
	[NVME_FW_LOAD_REQ_FIELD_OFFSET] = {
		.nlfi_vers = &nvme_vers_1v0,
		.nlfi_valid = nvme_fw_load_field_valid_offset,
		.nlfi_spec = "ofst",
		.nlfi_human = "offset",
		.nlfi_def_req = true,
		.nlfi_def_allow = true
	}
};

const size_t nvme_fw_load_nfields = ARRAY_SIZE(nvme_fw_load_fields);

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)
{
	return (nvme_field_range_check(field, NVME_FW_SLOT_MIN,
	    data->vcd_id->id_frmw.fw_nslot, msg, msglen, slot));
}

/*
 * This validation function represents an area of improvement that we'd like to
 * figure out in the future. Immediate firmware activations are only supported
 * in NVMe 1.3, so while it's a bad value prior to NVMe 1.3, that is a somewhat
 * confusing error. In addition, the various boot partition updates are not
 * supported, so it's not a bad value to the spec, but just to us.
 */
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)
{
	uint64_t max = NVME_FWC_ACTIVATE;

	if (nvme_vers_atleast(data->vcd_vers, &nvme_vers_1v3)) {
		max = NVME_FWC_ACTIVATE_IMMED;
	}

	return (nvme_field_range_check(field, 0, max, msg, msglen, act));
}

const nvme_field_info_t nvme_fw_commit_fields[] = {
	[NVME_FW_COMMIT_REQ_FIELD_SLOT] = {
		.nlfi_vers = &nvme_vers_1v0,
		.nlfi_valid = nvme_fw_commit_field_valid_slot,
		.nlfi_spec = "fs",
		.nlfi_human = "firmware slot",
		.nlfi_def_req = true,
		.nlfi_def_allow = true
	},
	[NVME_FW_COMMIT_REQ_FIELD_ACT] = {
		.nlfi_vers = &nvme_vers_1v0,
		.nlfi_valid = nvme_fw_commit_field_valid_act,
		.nlfi_spec = "ca",
		.nlfi_human = "commit action",
		.nlfi_def_req = true,
		.nlfi_def_allow = true
	}
};

const size_t nvme_fw_commit_nfields = ARRAY_SIZE(nvme_fw_commit_fields);