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 #include <strings.h>
49 #endif
50
51 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)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
nvme_log_field_supported_offset(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,char * msg,size_t msglen)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
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)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
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)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 size_t nvme_log_nfields = ARRAY_SIZE(nvme_log_fields);
198
199 static uint64_t
nvme_lpd_error_len(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)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
nvme_lpd_health_scope(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)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
nvme_lpd_changens_sup(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)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
nvme_lpd_cmdeff_sup(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)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
nvme_lpd_pev_sup(const nvme_valid_ctrl_data_t * data,const nvme_log_page_info_t * lpi)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
nvme_lpd_pev_len(uint64_t * outp,const void * data,size_t len)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 /*
262 * The short names here correspond to the well defined names in nvmeadm(8) and
263 * libnvme(3LIB) that users expect to be able to use. Please do not change them
264 * without accounting for aliases and backwards compatibility.
265 */
266 const nvme_log_page_info_t nvme_std_log_pages[] = { {
267 .nlpi_short = "suplog",
268 .nlpi_human = "Supported Log Pages",
269 .nlpi_lid = NVME_LOGPAGE_SUP,
270 .nlpi_csi = NVME_CSI_NVM,
271 .nlpi_vers = &nvme_vers_2v0,
272 .nlpi_kind = NVME_LOG_ID_MANDATORY,
273 .nlpi_source = NVME_LOG_DISC_S_SPEC,
274 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
275 .nlpi_len = sizeof (nvme_suplog_log_t)
276 }, {
277 .nlpi_short = "error",
278 .nlpi_human = "Error information",
279 .nlpi_lid = NVME_LOGPAGE_ERROR,
280 .nlpi_csi = NVME_CSI_NVM,
281 .nlpi_vers = &nvme_vers_1v0,
282 .nlpi_kind = NVME_LOG_ID_MANDATORY,
283 .nlpi_source = NVME_LOG_DISC_S_SPEC,
284 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
285 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
286 .nlpi_len_func = nvme_lpd_error_len
287 }, {
288 .nlpi_short = "health",
289 .nlpi_human = "SMART / Health information",
290 .nlpi_lid = NVME_LOGPAGE_HEALTH,
291 .nlpi_csi = NVME_CSI_NVM,
292 .nlpi_vers = &nvme_vers_1v0,
293 .nlpi_kind = NVME_LOG_ID_MANDATORY,
294 .nlpi_source = NVME_LOG_DISC_S_SPEC | NVME_LOG_DISC_S_ID_CTRL,
295 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
296 .nlpi_scope_func = nvme_lpd_health_scope,
297 .nlpi_len = sizeof (nvme_health_log_t)
298 }, {
299 .nlpi_short = "firmware",
300 .nlpi_human = "Firmware Slot Information",
301 .nlpi_lid = NVME_LOGPAGE_FWSLOT,
302 .nlpi_csi = NVME_CSI_NVM,
303 .nlpi_vers = &nvme_vers_1v0,
304 .nlpi_kind = NVME_LOG_ID_MANDATORY,
305 .nlpi_source = NVME_LOG_DISC_S_SPEC,
306 .nlpi_disc = 0,
307 .nlpi_scope = NVME_LOG_SCOPE_NVM,
308 .nlpi_len = sizeof (nvme_fwslot_log_t),
309 }, {
310 .nlpi_short = "changens",
311 .nlpi_human = "changed namespaces",
312 .nlpi_lid = NVME_LOGPAGE_NSCHANGE,
313 .nlpi_csi = NVME_CSI_NVM,
314 .nlpi_vers = &nvme_vers_1v2,
315 .nlpi_sup_func = nvme_lpd_changens_sup,
316 .nlpi_kind = NVME_LOG_ID_OPTIONAL,
317 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
318 .nlpi_disc = NVME_LOG_DISC_F_NEED_RAE,
319 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
320 .nlpi_len = sizeof (nvme_nschange_list_t)
321 }, {
322 .nlpi_short = "cmdeff",
323 .nlpi_human = "commands supported and effects",
324 .nlpi_lid = NVME_LOGPAGE_CMDSUP,
325 .nlpi_csi = NVME_CSI_NVM,
326 .nlpi_vers = &nvme_vers_1v2,
327 .nlpi_sup_func = nvme_lpd_cmdeff_sup,
328 .nlpi_kind = NVME_LOG_ID_OPTIONAL,
329 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
330 .nlpi_scope = NVME_LOG_SCOPE_CTRL,
331 .nlpi_len = sizeof (nvme_cmdeff_log_t)
332 }, {
333 .nlpi_short = "pev",
334 .nlpi_human = "persistent event log",
335 .nlpi_lid = NVME_LOGPAGE_PEV,
336 .nlpi_csi = NVME_CSI_NVM,
337 .nlpi_vers = &nvme_vers_1v4,
338 .nlpi_sup_func = nvme_lpd_pev_sup,
339 .nlpi_kind = NVME_LOG_ID_OPTIONAL,
340 .nlpi_source = NVME_LOG_DISC_S_ID_CTRL,
341 .nlpi_disc = NVME_LOG_DISC_F_NEED_LSP,
342 .nlpi_scope = NVME_LOG_SCOPE_NVM,
343 .nlpi_len = sizeof (nvme_pev_log_t),
344 .nlpi_var_func = nvme_lpd_pev_len
345 } };
346
347 size_t nvme_std_log_npages = ARRAY_SIZE(nvme_std_log_pages);
348
349 nvme_log_disc_scope_t
nvme_log_page_info_scope(const nvme_log_page_info_t * info,const nvme_valid_ctrl_data_t * data)350 nvme_log_page_info_scope(const nvme_log_page_info_t *info,
351 const nvme_valid_ctrl_data_t *data)
352 {
353 if (info->nlpi_scope_func != NULL) {
354 return (info->nlpi_scope_func(data, info));
355 } else {
356 return (info->nlpi_scope);
357 }
358 }
359
360 uint64_t
nvme_log_page_info_size(const nvme_log_page_info_t * info,const nvme_valid_ctrl_data_t * data,bool * var)361 nvme_log_page_info_size(const nvme_log_page_info_t *info,
362 const nvme_valid_ctrl_data_t *data, bool *var)
363 {
364 uint64_t len;
365 *var = info->nlpi_var_func != NULL;
366
367 if (info->nlpi_len_func != NULL) {
368 len = info->nlpi_len_func(data, info);
369 } else {
370 len = info->nlpi_len;
371 }
372
373 /*
374 * Some vendor-specific log pages are not documented to have 4-byte
375 * aligned lengths. This means that to get the full log page you must
376 * round this up to ensure that you end up with a valid request. We opt
377 * to do this here rather than have to check every single log page data
378 * structure and fix it up manually. While it means consumers that are
379 * using this to ignore information about the type itself may
380 * erroneously display extra bytes (e.g. nvmeadm's default hex dumper),
381 * that's better than getting an error or truncating the data.
382 */
383 return (P2ROUNDUP(len, NVME_DWORD_SIZE));
384 }
385
386 bool
nvme_log_page_info_supported(const nvme_log_page_info_t * info,const nvme_valid_ctrl_data_t * data)387 nvme_log_page_info_supported(const nvme_log_page_info_t *info,
388 const nvme_valid_ctrl_data_t *data)
389 {
390 bool vers, sup_func;
391
392 if (info->nlpi_vers != NULL) {
393 vers = nvme_field_atleast(data, info->nlpi_vers);
394 } else {
395 vers = true;
396 }
397
398 if (info->nlpi_sup_func != NULL) {
399 sup_func = info->nlpi_sup_func(data, info);
400 } else {
401 sup_func = true;
402 }
403
404 return (vers && sup_func);
405 }
406