xref: /illumos-gate/usr/src/common/nvme/nvme_log.c (revision 7001e2dfbde60ab6bfa231a6025776e173dc4657)
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  * This file deals with validating and issuing various log page requests to an
18  * NVMe target. This contains information about all spec-based log pages. The
19  * get log page command has added a number of fields that have evolved over
20  * time. We validate that we're only sending commands to a device that we expect
21  * it to have a chance of understanding. In general, we only allow through
22  * unknown log pages that correspond to vendor-specific commands.
23  *
24  * We have two different tables of information that we use to drive and validate
25  * things here:
26  *
27  * 1) We have a list of fields that exist which include minimum controller
28  * versions and related functionality validation routines that operate off of
29  * the nvme_t. While tihs list includes things like the CSI and LID, these are
30  * things that may only be specified when we have a non-standard log page.
31  *
32  * 2) We then have a table of log pages that are supported which list which
33  * fields we allow for the device. Not all of this can be static.
34  *
35  * This file has been designed to be shareable between both userland and the
36  * kernel since the logic that libnvme wants to use is quite similar.
37  */
38 
39 #include "nvme_common.h"
40 
41 #include <sys/sysmacros.h>
42 #ifdef	_KERNEL
43 #include <sys/sunddi.h>
44 #include <sys/stdint.h>
45 #else
46 #include <stdio.h>
47 #include <inttypes.h>
48 #endif
49 
50 static bool
nvme_log_field_valid_lsp(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,uint64_t lsp,char * msg,size_t msglen)51 nvme_log_field_valid_lsp(const nvme_field_info_t *field,
52     const nvme_valid_ctrl_data_t *data, uint64_t lsp, char *msg, size_t msglen)
53 {
54 	uint64_t max;
55 
56 	if (nvme_field_atleast(data, &nvme_vers_2v0)) {
57 		max = NVME_LOG_MAX_LSP_2v0;
58 	} else {
59 		max = NVME_LOG_MAX_LSP;
60 	}
61 
62 	return (nvme_field_range_check(field, 0, max, msg, msglen, lsp));
63 }
64 
65 static bool
nvme_log_field_supported_offset(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,char * msg,size_t msglen)66 nvme_log_field_supported_offset(const nvme_field_info_t *field,
67     const nvme_valid_ctrl_data_t *data, char *msg, size_t msglen)
68 {
69 	if (data->vcd_id->id_lpa.lp_extsup != 0) {
70 		return (true);
71 	}
72 
73 	(void) snprintf(msg, msglen, "controller does not support field %s "
74 	    "(%s): missing extended data support in Log Page Attributes (LPA)",
75 	    field->nlfi_human, field->nlfi_spec);
76 	return (false);
77 }
78 
79 /*
80  * The offset is a full 64-bit byte value; however, it must be 4-byte aligned.
81  */
82 static bool
nvme_log_field_valid_offset(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,uint64_t size,char * msg,size_t msglen)83 nvme_log_field_valid_offset(const nvme_field_info_t *field,
84     const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen)
85 {
86 	if ((size % NVME_DWORD_SIZE) != 0) {
87 		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is "
88 		    "invalid: value must be %u-byte aligned", field->nlfi_human,
89 		    field->nlfi_spec, size, NVME_DWORD_SIZE);
90 		return (false);
91 	}
92 
93 	return (true);
94 }
95 
96 static bool
nvme_log_field_valid_size(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,uint64_t size,char * msg,size_t msglen)97 nvme_log_field_valid_size(const nvme_field_info_t *field,
98     const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen)
99 {
100 	uint64_t max = NVME_LOG_MAX_SIZE;
101 
102 	if (nvme_field_atleast(data, &nvme_vers_1v2) &&
103 	    data->vcd_id->id_lpa.lp_extsup != 0) {
104 		max = NVME_LOG_MAX_SIZE_1v2;
105 	}
106 
107 	/*
108 	 * The NVMe specification operates in terms of uint32_t (dword) units.
109 	 * Make sure that we are operating within that constraint.
110 	 */
111 	if ((size % 4) != 0) {
112 		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is "
113 		    "invalid: value must be 4-byte aligned", field->nlfi_human,
114 		    field->nlfi_spec, size);
115 		return (false);
116 	}
117 
118 	return (nvme_field_range_check(field, 4, max, msg, msglen, size));
119 }
120 
121 const nvme_field_info_t nvme_log_fields[] = {
122 	[NVME_LOG_REQ_FIELD_LID] = {
123 		.nlfi_vers = &nvme_vers_1v0,
124 		.nlfi_max_size = NVME_LOG_MAX_LID,
125 		.nlfi_spec = "lid",
126 		.nlfi_human = "log ID",
127 		.nlfi_def_req = true,
128 		.nlfi_def_allow = true
129 	},
130 	[NVME_LOG_REQ_FIELD_LSP] = {
131 		.nlfi_vers = &nvme_vers_1v3,
132 		.nlfi_valid = nvme_log_field_valid_lsp,
133 		.nlfi_spec = "lsp",
134 		.nlfi_human = "log specific field",
135 		.nlfi_def_req = false,
136 		.nlfi_def_allow = true
137 	},
138 	[NVME_LOG_REQ_FIELD_LSI] = {
139 		.nlfi_vers = &nvme_vers_1v4,
140 		.nlfi_max_size = NVME_LOG_MAX_LSI,
141 		.nlfi_spec = "lsi",
142 		.nlfi_human = "log specific ID",
143 		.nlfi_def_req = false,
144 		.nlfi_def_allow = true
145 	},
146 	[NVME_LOG_REQ_FIELD_SIZE] = {
147 		.nlfi_vers = &nvme_vers_1v0,
148 		.nlfi_valid = nvme_log_field_valid_size,
149 		.nlfi_spec = "dptr/numd",
150 		.nlfi_human = "output",
151 		.nlfi_def_req = true,
152 		.nlfi_def_allow = true
153 	},
154 	[NVME_LOG_REQ_FIELD_CSI] = {
155 		.nlfi_vers = &nvme_vers_2v0,
156 		/*
157 		 * This has the field's maximum range right now, though NVMe 2.0
158 		 * only defines a few values. Because the kernel only allows
159 		 * through known log pages, we don't really bother to check the
160 		 * condensed range and allow vendor-specific logs to go wild.
161 		 */
162 		.nlfi_max_size = NVME_LOG_MAX_CSI,
163 		.nlfi_spec = "csi",
164 		.nlfi_human = "command set ID",
165 		.nlfi_def_req = false,
166 		.nlfi_def_allow = true
167 	},
168 	[NVME_LOG_REQ_FIELD_RAE] = {
169 		.nlfi_vers = &nvme_vers_1v3,
170 		.nlfi_max_size = NVME_LOG_MAX_RAE,
171 		.nlfi_spec = "rae",
172 		.nlfi_human = "retain asynchronous event",
173 		.nlfi_def_req = false,
174 		.nlfi_def_allow = true
175 	},
176 	[NVME_LOG_REQ_FIELD_OFFSET] = {
177 		.nlfi_vers = &nvme_vers_1v2,
178 		.nlfi_sup = nvme_log_field_supported_offset,
179 		.nlfi_valid = nvme_log_field_valid_offset,
180 		.nlfi_max_size = NVME_LOG_MAX_OFFSET,
181 		.nlfi_spec = "lpo",
182 		.nlfi_human = "log offset",
183 		.nlfi_def_req = false,
184 		.nlfi_def_allow = true
185 	},
186 	[NVME_LOG_REQ_FIELD_NSID] = {
187 		.nlfi_vers = &nvme_vers_1v0,
188 		.nlfi_valid = nvme_field_valid_nsid,
189 		.nlfi_spec = "nsid",
190 		.nlfi_human = "namespace ID",
191 		.nlfi_def_req = false,
192 		.nlfi_def_allow = true
193 	}
194 };
195 
196 size_t nvme_log_nfields = ARRAY_SIZE(nvme_log_fields);
197 
198 static uint64_t
nvme_lpd_error_len(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)199 nvme_lpd_error_len(const nvme_valid_ctrl_data_t *data,
200     const nvme_log_page_info_t *lpi)
201 {
202 	const uint64_t nents = data->vcd_id->id_elpe + 1;
203 	const uint64_t logsz = nents * sizeof (nvme_error_log_entry_t);
204 
205 	return (logsz);
206 }
207 
208 static nvme_log_disc_scope_t
nvme_lpd_health_scope(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)209 nvme_lpd_health_scope(const nvme_valid_ctrl_data_t *data,
210     const nvme_log_page_info_t *lpi)
211 {
212 	nvme_log_disc_scope_t ret = NVME_LOG_SCOPE_CTRL;
213 
214 	if (nvme_field_atleast(data, &nvme_vers_1v0) &&
215 	    data->vcd_id->id_lpa.lp_smart != 0) {
216 		ret |= NVME_LOG_SCOPE_NS;
217 	}
218 
219 	return (ret);
220 }
221 
222 static bool
nvme_lpd_changens_sup(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)223 nvme_lpd_changens_sup(const nvme_valid_ctrl_data_t *data,
224     const nvme_log_page_info_t *lpi)
225 {
226 	return (nvme_field_atleast(data, &nvme_vers_1v2) &&
227 	    data->vcd_id->id_oaes.oaes_nsan != 0);
228 }
229 
230 static bool
nvme_lpd_cmdeff_sup(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)231 nvme_lpd_cmdeff_sup(const nvme_valid_ctrl_data_t *data,
232     const nvme_log_page_info_t *lpi)
233 {
234 	return (nvme_field_atleast(data, &nvme_vers_1v2) &&
235 	    data->vcd_id->id_lpa.lp_cmdeff != 0);
236 }
237 
238 /*
239  * The short names here correspond to the well defined names in nvmeadm(8) and
240  * libnvme(3LIB) that users expect to be able to use. Please do not change them
241  * without accounting for aliases and backwards compatibility.
242  */
243 const nvme_log_page_info_t nvme_std_log_pages[] = { {
244 	.nlpi_short = "suplog",
245 	.nlpi_human = "Supported Log Pages",
246 	.nlpi_lid = NVME_LOGPAGE_SUP,
247 	.nlpi_csi = NVME_CSI_NVM,
248 	.nlpi_vers = &nvme_vers_2v0,
249 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
250 	.nlpi_source = NVME_LOG_DISC_S_SPEC,
251 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
252 	.nlpi_len = sizeof (nvme_suplog_log_t)
253 }, {
254 	.nlpi_short = "error",
255 	.nlpi_human = "Error information",
256 	.nlpi_lid = NVME_LOGPAGE_ERROR,
257 	.nlpi_csi = NVME_CSI_NVM,
258 	.nlpi_vers = &nvme_vers_1v0,
259 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
260 	.nlpi_source = NVME_LOG_DISC_S_SPEC,
261 	.nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
262 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
263 	.nlpi_len_func = nvme_lpd_error_len
264 }, {
265 	.nlpi_short = "health",
266 	.nlpi_human = "SMART / Health information",
267 	.nlpi_lid = NVME_LOGPAGE_HEALTH,
268 	.nlpi_csi = NVME_CSI_NVM,
269 	.nlpi_vers = &nvme_vers_1v0,
270 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
271 	.nlpi_source = NVME_LOG_DISC_S_SPEC | NVME_LOG_DISC_S_ID_CTRL,
272 	.nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
273 	.nlpi_scope_func = nvme_lpd_health_scope,
274 	.nlpi_len = sizeof (nvme_health_log_t)
275 }, {
276 	.nlpi_short = "firmware",
277 	.nlpi_human = "Firmware Slot Information",
278 	.nlpi_lid = NVME_LOGPAGE_FWSLOT,
279 	.nlpi_csi = NVME_CSI_NVM,
280 	.nlpi_vers = &nvme_vers_1v0,
281 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
282 	.nlpi_source = NVME_LOG_DISC_S_SPEC,
283 	.nlpi_disc = 0,
284 	.nlpi_scope = NVME_LOG_SCOPE_NVM,
285 	.nlpi_len = sizeof (nvme_fwslot_log_t),
286 }, {
287 	.nlpi_short = "changens",
288 	.nlpi_human = "changed namespaces",
289 	.nlpi_lid = NVME_LOGPAGE_NSCHANGE,
290 	.nlpi_csi = NVME_CSI_NVM,
291 	.nlpi_vers = &nvme_vers_1v2,
292 	.nlpi_sup_func = nvme_lpd_changens_sup,
293 	.nlpi_kind = NVME_LOG_ID_OPTIONAL,
294 	.nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
295 	.nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
296 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
297 	.nlpi_len = sizeof (nvme_nschange_list_t)
298 }, {
299 	.nlpi_short = "cmdeff",
300 	.nlpi_human = "commands supported and effects",
301 	.nlpi_lid = NVME_LOGPAGE_CMDSUP,
302 	.nlpi_csi = NVME_CSI_NVM,
303 	.nlpi_vers = &nvme_vers_1v2,
304 	.nlpi_sup_func = nvme_lpd_cmdeff_sup,
305 	.nlpi_kind = NVME_LOG_ID_OPTIONAL,
306 	.nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
307 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
308 	.nlpi_len = sizeof (nvme_cmdeff_log_t)
309 }  };
310 
311 size_t nvme_std_log_npages = ARRAY_SIZE(nvme_std_log_pages);
312 
313 nvme_log_disc_scope_t
nvme_log_page_info_scope(const nvme_log_page_info_t * info,const nvme_valid_ctrl_data_t * data)314 nvme_log_page_info_scope(const nvme_log_page_info_t *info,
315     const nvme_valid_ctrl_data_t *data)
316 {
317 	if (info->nlpi_scope_func != NULL) {
318 		return (info->nlpi_scope_func(data, info));
319 	} else {
320 		return (info->nlpi_scope);
321 	}
322 }
323 
324 uint64_t
nvme_log_page_info_size(const nvme_log_page_info_t * info,const nvme_valid_ctrl_data_t * data,bool * var)325 nvme_log_page_info_size(const nvme_log_page_info_t *info,
326     const nvme_valid_ctrl_data_t *data, bool *var)
327 {
328 	uint64_t len;
329 	*var = info->nlpi_var_func != NULL;
330 
331 	if (info->nlpi_len_func != NULL) {
332 		len = info->nlpi_len_func(data, info);
333 	} else {
334 		len = info->nlpi_len;
335 	}
336 
337 	/*
338 	 * Some vendor-specific log pages are not documented to have 4-byte
339 	 * aligned lengths. This means that to get the full log page you must
340 	 * round this up to ensure that you end up with a valid request. We opt
341 	 * to do this here rather than have to check every single log page data
342 	 * structure and fix it up manually. While it means consumers that are
343 	 * using this to ignore information about the type itself may
344 	 * erroneously display extra bytes (e.g. nvmeadm's default hex dumper),
345 	 * that's better than getting an error or truncating the data.
346 	 */
347 	return (P2ROUNDUP(len, NVME_DWORD_SIZE));
348 }
349 
350 bool
nvme_log_page_info_supported(const nvme_log_page_info_t * info,const nvme_valid_ctrl_data_t * data)351 nvme_log_page_info_supported(const nvme_log_page_info_t *info,
352     const nvme_valid_ctrl_data_t *data)
353 {
354 	bool vers, sup_func;
355 
356 	if (info->nlpi_vers != NULL) {
357 		vers = nvme_field_atleast(data, info->nlpi_vers);
358 	} else {
359 		vers = true;
360 	}
361 
362 	if (info->nlpi_sup_func != NULL) {
363 		sup_func = info->nlpi_sup_func(data, info);
364 	} else {
365 		sup_func = true;
366 	}
367 
368 	return (vers && sup_func);
369 }
370