xref: /illumos-gate/usr/src/common/nvme/nvme_log.c (revision 6fc89bfc8e69fd45d8778b2f0ad45efc0ded99ed)
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 2025 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 #include <strings.h>
49 #endif
50 
51 static bool
52 nvme_log_field_valid_lsp(const nvme_field_info_t *field,
53     const nvme_valid_ctrl_data_t *data, uint64_t lsp, char *msg, size_t msglen)
54 {
55 	uint64_t max;
56 
57 	if (nvme_field_atleast(data, &nvme_vers_2v0)) {
58 		max = NVME_LOG_MAX_LSP_2v0;
59 	} else {
60 		max = NVME_LOG_MAX_LSP;
61 	}
62 
63 	return (nvme_field_range_check(field, 0, max, msg, msglen, lsp));
64 }
65 
66 static bool
67 nvme_log_field_supported_offset(const nvme_field_info_t *field,
68     const nvme_valid_ctrl_data_t *data, char *msg, size_t msglen)
69 {
70 	if (data->vcd_id->id_lpa.lp_extsup != 0) {
71 		return (true);
72 	}
73 
74 	(void) snprintf(msg, msglen, "controller does not support field %s "
75 	    "(%s): missing extended data support in Log Page Attributes (LPA)",
76 	    field->nlfi_human, field->nlfi_spec);
77 	return (false);
78 }
79 
80 /*
81  * The offset is a full 64-bit byte value; however, it must be 4-byte aligned.
82  */
83 static bool
84 nvme_log_field_valid_offset(const nvme_field_info_t *field,
85     const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen)
86 {
87 	if ((size % NVME_DWORD_SIZE) != 0) {
88 		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is "
89 		    "invalid: value must be %u-byte aligned", field->nlfi_human,
90 		    field->nlfi_spec, size, NVME_DWORD_SIZE);
91 		return (false);
92 	}
93 
94 	return (true);
95 }
96 
97 static bool
98 nvme_log_field_valid_size(const nvme_field_info_t *field,
99     const nvme_valid_ctrl_data_t *data, uint64_t size, char *msg, size_t msglen)
100 {
101 	uint64_t max = NVME_LOG_MAX_SIZE;
102 
103 	if (nvme_field_atleast(data, &nvme_vers_1v2) &&
104 	    data->vcd_id->id_lpa.lp_extsup != 0) {
105 		max = NVME_LOG_MAX_SIZE_1v2;
106 	}
107 
108 	/*
109 	 * The NVMe specification operates in terms of uint32_t (dword) units.
110 	 * Make sure that we are operating within that constraint.
111 	 */
112 	if ((size % 4) != 0) {
113 		(void) snprintf(msg, msglen, "%s (%s) value 0x%" PRIx64 " is "
114 		    "invalid: value must be 4-byte aligned", field->nlfi_human,
115 		    field->nlfi_spec, size);
116 		return (false);
117 	}
118 
119 	return (nvme_field_range_check(field, 4, max, msg, msglen, size));
120 }
121 
122 const nvme_field_info_t nvme_log_fields[] = {
123 	[NVME_LOG_REQ_FIELD_LID] = {
124 		.nlfi_vers = &nvme_vers_1v0,
125 		.nlfi_max_size = NVME_LOG_MAX_LID,
126 		.nlfi_spec = "lid",
127 		.nlfi_human = "log ID",
128 		.nlfi_def_req = true,
129 		.nlfi_def_allow = true
130 	},
131 	[NVME_LOG_REQ_FIELD_LSP] = {
132 		.nlfi_vers = &nvme_vers_1v3,
133 		.nlfi_valid = nvme_log_field_valid_lsp,
134 		.nlfi_spec = "lsp",
135 		.nlfi_human = "log specific field",
136 		.nlfi_def_req = false,
137 		.nlfi_def_allow = true
138 	},
139 	[NVME_LOG_REQ_FIELD_LSI] = {
140 		.nlfi_vers = &nvme_vers_1v4,
141 		.nlfi_max_size = NVME_LOG_MAX_LSI,
142 		.nlfi_spec = "lsi",
143 		.nlfi_human = "log specific ID",
144 		.nlfi_def_req = false,
145 		.nlfi_def_allow = true
146 	},
147 	[NVME_LOG_REQ_FIELD_SIZE] = {
148 		.nlfi_vers = &nvme_vers_1v0,
149 		.nlfi_valid = nvme_log_field_valid_size,
150 		.nlfi_spec = "dptr/numd",
151 		.nlfi_human = "output",
152 		.nlfi_def_req = true,
153 		.nlfi_def_allow = true
154 	},
155 	[NVME_LOG_REQ_FIELD_CSI] = {
156 		.nlfi_vers = &nvme_vers_2v0,
157 		/*
158 		 * This has the field's maximum range right now, though NVMe 2.0
159 		 * only defines a few values. Because the kernel only allows
160 		 * through known log pages, we don't really bother to check the
161 		 * condensed range and allow vendor-specific logs to go wild.
162 		 */
163 		.nlfi_max_size = NVME_LOG_MAX_CSI,
164 		.nlfi_spec = "csi",
165 		.nlfi_human = "command set ID",
166 		.nlfi_def_req = false,
167 		.nlfi_def_allow = true
168 	},
169 	[NVME_LOG_REQ_FIELD_RAE] = {
170 		.nlfi_vers = &nvme_vers_1v3,
171 		.nlfi_max_size = NVME_LOG_MAX_RAE,
172 		.nlfi_spec = "rae",
173 		.nlfi_human = "retain asynchronous event",
174 		.nlfi_def_req = false,
175 		.nlfi_def_allow = true
176 	},
177 	[NVME_LOG_REQ_FIELD_OFFSET] = {
178 		.nlfi_vers = &nvme_vers_1v2,
179 		.nlfi_sup = nvme_log_field_supported_offset,
180 		.nlfi_valid = nvme_log_field_valid_offset,
181 		.nlfi_max_size = NVME_LOG_MAX_OFFSET,
182 		.nlfi_spec = "lpo",
183 		.nlfi_human = "log offset",
184 		.nlfi_def_req = false,
185 		.nlfi_def_allow = true
186 	},
187 	[NVME_LOG_REQ_FIELD_NSID] = {
188 		.nlfi_vers = &nvme_vers_1v0,
189 		.nlfi_valid = nvme_field_valid_nsid,
190 		.nlfi_spec = "nsid",
191 		.nlfi_human = "namespace ID",
192 		.nlfi_def_req = false,
193 		.nlfi_def_allow = true
194 	}
195 };
196 
197 const size_t nvme_log_nfields = ARRAY_SIZE(nvme_log_fields);
198 
199 static uint64_t
200 nvme_lpd_error_len(const nvme_valid_ctrl_data_t *data,
201     const nvme_log_page_info_t *lpi)
202 {
203 	const uint64_t nents = data->vcd_id->id_elpe + 1;
204 	const uint64_t logsz = nents * sizeof (nvme_error_log_entry_t);
205 
206 	return (logsz);
207 }
208 
209 static nvme_log_disc_scope_t
210 nvme_lpd_health_scope(const nvme_valid_ctrl_data_t *data,
211     const nvme_log_page_info_t *lpi)
212 {
213 	nvme_log_disc_scope_t ret = NVME_LOG_SCOPE_CTRL;
214 
215 	if (nvme_field_atleast(data, &nvme_vers_1v0) &&
216 	    data->vcd_id->id_lpa.lp_smart != 0) {
217 		ret |= NVME_LOG_SCOPE_NS;
218 	}
219 
220 	return (ret);
221 }
222 
223 static bool
224 nvme_lpd_changens_sup(const nvme_valid_ctrl_data_t *data,
225     const nvme_log_page_info_t *lpi)
226 {
227 	return (nvme_field_atleast(data, &nvme_vers_1v2) &&
228 	    data->vcd_id->id_oaes.oaes_nsan != 0);
229 }
230 
231 static bool
232 nvme_lpd_cmdeff_sup(const nvme_valid_ctrl_data_t *data,
233     const nvme_log_page_info_t *lpi)
234 {
235 	return (nvme_field_atleast(data, &nvme_vers_1v2) &&
236 	    data->vcd_id->id_lpa.lp_cmdeff != 0);
237 }
238 
239 static bool
240 nvme_lpd_pev_sup(const nvme_valid_ctrl_data_t *data,
241     const nvme_log_page_info_t *lpi)
242 {
243 	return (nvme_field_atleast(data, &nvme_vers_1v4) &&
244 	    data->vcd_id->id_lpa.lp_persist != 0);
245 }
246 
247 static bool
248 nvme_lpd_pev_len(uint64_t *outp, const void *data, size_t len)
249 {
250 	nvme_pev_log_t pev;
251 
252 	if (len < sizeof (pev)) {
253 		return (false);
254 	}
255 
256 	(void) memcpy(&pev, data, sizeof (pev));
257 	*outp = pev.pel_tll;
258 	return (true);
259 }
260 
261 static bool
262 nvme_lpd_telemetry_sup(const nvme_valid_ctrl_data_t *data,
263     const nvme_log_page_info_t *lpi)
264 {
265 	return (nvme_field_atleast(data, &nvme_vers_1v3) &&
266 	    data->vcd_id->id_lpa.lp_telemetry != 0);
267 }
268 
269 static bool
270 nvme_lpd_telemetry_len(uint64_t *outp, const void *data, size_t len)
271 {
272 	nvme_telemetry_log_t telem;
273 	uint64_t nblks;
274 
275 	if (len < sizeof (telem)) {
276 		return (false);
277 	}
278 
279 	(void) memcpy(&telem, data, sizeof (telem));
280 
281 	/*
282 	 * See if we have section 4 information, then fall back to 3, 2, and
283 	 * finally 1 to determine the size. We then have to add the header.
284 	 */
285 	nblks = telem.ntl_thda4lb;
286 	if (nblks == 0)
287 		nblks = telem.ntl_thda3lb;
288 	if (nblks == 0)
289 		nblks = telem.ntl_thda2lb;
290 	if (nblks == 0)
291 		nblks = telem.ntl_thda1lb;
292 	nblks++;
293 	*outp = nblks * sizeof (telem);
294 	return (true);
295 }
296 
297 /*
298  * The short names here correspond to the well defined names in nvmeadm(8) and
299  * libnvme(3LIB) that users expect to be able to use. Please do not change them
300  * without accounting for aliases and backwards compatibility. The index of the
301  * supported log pages entry is expected to be the first entry here. This is
302  * relied upon by log discovery in nvme_log_discover_fetch_sup_logs().
303  */
304 const nvme_log_page_info_t nvme_std_log_pages[] = { {
305 	.nlpi_short = "suplog",
306 	.nlpi_human = "Supported Log Pages",
307 	.nlpi_lid = NVME_LOGPAGE_SUP,
308 	.nlpi_csi = NVME_CSI_NVM,
309 	.nlpi_vers = &nvme_vers_2v0,
310 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
311 	.nlpi_source = NVME_LOG_DISC_S_SPEC,
312 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
313 	.nlpi_len = sizeof (nvme_suplog_log_t)
314 }, {
315 	.nlpi_short = "error",
316 	.nlpi_human = "Error information",
317 	.nlpi_lid = NVME_LOGPAGE_ERROR,
318 	.nlpi_csi = NVME_CSI_NVM,
319 	.nlpi_vers = &nvme_vers_1v0,
320 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
321 	.nlpi_source = NVME_LOG_DISC_S_SPEC,
322 	.nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
323 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
324 	.nlpi_len_func = nvme_lpd_error_len
325 }, {
326 	.nlpi_short = "health",
327 	.nlpi_human = "SMART / Health information",
328 	.nlpi_lid = NVME_LOGPAGE_HEALTH,
329 	.nlpi_csi = NVME_CSI_NVM,
330 	.nlpi_vers = &nvme_vers_1v0,
331 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
332 	.nlpi_source = NVME_LOG_DISC_S_SPEC | NVME_LOG_DISC_S_ID_CTRL,
333 	.nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
334 	.nlpi_scope_func = nvme_lpd_health_scope,
335 	.nlpi_len = sizeof (nvme_health_log_t)
336 }, {
337 	.nlpi_short = "firmware",
338 	.nlpi_human = "Firmware Slot Information",
339 	.nlpi_lid = NVME_LOGPAGE_FWSLOT,
340 	.nlpi_csi = NVME_CSI_NVM,
341 	.nlpi_vers = &nvme_vers_1v0,
342 	.nlpi_kind = NVME_LOG_ID_MANDATORY,
343 	.nlpi_source = NVME_LOG_DISC_S_SPEC,
344 	.nlpi_disc = 0,
345 	.nlpi_scope = NVME_LOG_SCOPE_NVM,
346 	.nlpi_len = sizeof (nvme_fwslot_log_t),
347 }, {
348 	.nlpi_short = "changens",
349 	.nlpi_human = "changed namespaces",
350 	.nlpi_lid = NVME_LOGPAGE_NSCHANGE,
351 	.nlpi_csi = NVME_CSI_NVM,
352 	.nlpi_vers = &nvme_vers_1v2,
353 	.nlpi_sup_func = nvme_lpd_changens_sup,
354 	.nlpi_kind = NVME_LOG_ID_OPTIONAL,
355 	.nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
356 	.nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
357 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
358 	.nlpi_len = sizeof (nvme_nschange_list_t)
359 }, {
360 	.nlpi_short = "cmdeff",
361 	.nlpi_human = "commands supported and effects",
362 	.nlpi_lid = NVME_LOGPAGE_CMDSUP,
363 	.nlpi_csi = NVME_CSI_NVM,
364 	.nlpi_vers = &nvme_vers_1v2,
365 	.nlpi_sup_func = nvme_lpd_cmdeff_sup,
366 	.nlpi_kind = NVME_LOG_ID_OPTIONAL,
367 	.nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
368 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
369 	.nlpi_len = sizeof (nvme_cmdeff_log_t)
370 }, {
371 	.nlpi_short = "pev",
372 	.nlpi_human = "persistent event log",
373 	.nlpi_lid = NVME_LOGPAGE_PEV,
374 	.nlpi_csi = NVME_CSI_NVM,
375 	.nlpi_vers = &nvme_vers_1v4,
376 	.nlpi_sup_func = nvme_lpd_pev_sup,
377 	.nlpi_kind = NVME_LOG_ID_OPTIONAL,
378 	.nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
379 	.nlpi_disc = NVME_LOG_DISC_F_NEED_LSP,
380 	.nlpi_scope = NVME_LOG_SCOPE_NVM,
381 	.nlpi_len = sizeof (nvme_pev_log_t),
382 	.nlpi_var_func = nvme_lpd_pev_len
383 }, {
384 	.nlpi_short = "telemetry",
385 	.nlpi_human = "telemetry host-initiated",
386 	.nlpi_lid = NVME_LOGPAGE_TELMHOST,
387 	.nlpi_csi = NVME_CSI_NVM,
388 	.nlpi_vers = &nvme_vers_1v3,
389 	.nlpi_sup_func = nvme_lpd_telemetry_sup,
390 	.nlpi_kind = NVME_LOG_ID_OPTIONAL,
391 	.nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
392 	.nlpi_disc = NVME_LOG_DISC_F_NEED_LSP,
393 	.nlpi_scope = NVME_LOG_SCOPE_CTRL,
394 	.nlpi_len = sizeof (nvme_telemetry_log_t),
395 	.nlpi_var_func = nvme_lpd_telemetry_len
396 } };
397 
398 const size_t nvme_std_log_npages = ARRAY_SIZE(nvme_std_log_pages);
399 
400 nvme_log_disc_scope_t
401 nvme_log_page_info_scope(const nvme_log_page_info_t *info,
402     const nvme_valid_ctrl_data_t *data)
403 {
404 	if (info->nlpi_scope_func != NULL) {
405 		return (info->nlpi_scope_func(data, info));
406 	} else {
407 		return (info->nlpi_scope);
408 	}
409 }
410 
411 uint64_t
412 nvme_log_page_info_size(const nvme_log_page_info_t *info,
413     const nvme_valid_ctrl_data_t *data, bool *var)
414 {
415 	uint64_t len;
416 	*var = info->nlpi_var_func != NULL;
417 
418 	if (info->nlpi_len_func != NULL) {
419 		len = info->nlpi_len_func(data, info);
420 	} else {
421 		len = info->nlpi_len;
422 	}
423 
424 	/*
425 	 * Some vendor-specific log pages are not documented to have 4-byte
426 	 * aligned lengths. This means that to get the full log page you must
427 	 * round this up to ensure that you end up with a valid request. We opt
428 	 * to do this here rather than have to check every single log page data
429 	 * structure and fix it up manually. While it means consumers that are
430 	 * using this to ignore information about the type itself may
431 	 * erroneously display extra bytes (e.g. nvmeadm's default hex dumper),
432 	 * that's better than getting an error or truncating the data.
433 	 */
434 	return (P2ROUNDUP(len, NVME_DWORD_SIZE));
435 }
436 
437 bool
438 nvme_log_page_info_supported(const nvme_log_page_info_t *info,
439     const nvme_valid_ctrl_data_t *data)
440 {
441 	bool vers, sup_func;
442 
443 	if (info->nlpi_vers != NULL) {
444 		vers = nvme_field_atleast(data, info->nlpi_vers);
445 	} else {
446 		vers = true;
447 	}
448 
449 	if (info->nlpi_sup_func != NULL) {
450 		sup_func = info->nlpi_sup_func(data, info);
451 	} else {
452 		sup_func = true;
453 	}
454 
455 	return (vers && sup_func);
456 }
457