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 2026 Oxide Computer Company
14 */
15
16 /*
17 * This implements all of the libnvme log discovery and log page execution
18 * functions.
19 *
20 * In NVMe 1.0 there were just three mandatory log pages. These are the classic
21 * Error, SMART, and firmware log pages. NVMe 1.1 added an optional log page for
22 * NVM devices. Specifically this is the Reservation Log page. This was
23 * indicated by the controller's ONCS field. Version 1.1 also introduced the Log
24 * Page Attributes (LPA) field which is how additional pages were indicated as
25 * being supported when not part of something like ONCS.
26 *
27 * Beginning in NVMe 1.2, many more log pages were added that were optional. In
28 * particular, the changed namespace list and command effects log. The former
29 * has support indicated via a bit in OAES (though this was not clarified until
30 * NVMe 1.3) while the latter is in the LPA field. NVMe 1.2 also added the
31 * ability for the Get Log Page to support larger amounts of data. The last
32 * major piece of 1.2 was the addition of fabrics related log pages. Those are
33 * not currently supported here.
34 *
35 * NVMe 1.3 and 1.4 continued the trend of adding log pages that are generally
36 * optional, but may be required given a specific set of features being enabled.
37 *
38 * The largest change for log pages is in NVMe 2.0. It added a specific means of
39 * indicating a command set for a given log page and also added the ability to
40 * query all the supported log pages. This has existed previously, but only
41 * through vendor specific means.
42 */
43
44 #include <string.h>
45 #include <upanic.h>
46 #include <sys/sysmacros.h>
47 #include <sys/debug.h>
48 #include <unistd.h>
49
50 #include "libnvme_impl.h"
51
52 void
nvme_log_disc_free(nvme_log_disc_t * disc)53 nvme_log_disc_free(nvme_log_disc_t *disc)
54 {
55 free(disc);
56 }
57
58 const char *
nvme_log_disc_name(const nvme_log_disc_t * disc)59 nvme_log_disc_name(const nvme_log_disc_t *disc)
60 {
61 return (disc->nld_short);
62 }
63
64 const char *
nvme_log_disc_desc(const nvme_log_disc_t * disc)65 nvme_log_disc_desc(const nvme_log_disc_t *disc)
66 {
67 return (disc->nld_desc);
68 }
69
70 const char *const *
nvme_log_disc_aliases(const nvme_log_disc_t * disc)71 nvme_log_disc_aliases(const nvme_log_disc_t *disc)
72 {
73 return (disc->nld_aliases);
74 }
75
76 size_t
nvme_log_disc_naliases(const nvme_log_disc_t * disc)77 nvme_log_disc_naliases(const nvme_log_disc_t *disc)
78 {
79 return (disc->nld_naliases);
80 }
81
82 nvme_csi_t
nvme_log_disc_csi(const nvme_log_disc_t * disc)83 nvme_log_disc_csi(const nvme_log_disc_t *disc)
84 {
85 return (disc->nld_csi);
86 }
87
88 uint32_t
nvme_log_disc_lid(const nvme_log_disc_t * disc)89 nvme_log_disc_lid(const nvme_log_disc_t *disc)
90 {
91 return (disc->nld_lid);
92 }
93
94 nvme_log_disc_kind_t
nvme_log_disc_kind(const nvme_log_disc_t * disc)95 nvme_log_disc_kind(const nvme_log_disc_t *disc)
96 {
97 return (disc->nld_kind);
98 }
99
100 nvme_log_disc_source_t
nvme_log_disc_sources(const nvme_log_disc_t * disc)101 nvme_log_disc_sources(const nvme_log_disc_t *disc)
102 {
103 return (disc->nld_srcs);
104 }
105
106 nvme_log_disc_fields_t
nvme_log_disc_fields(const nvme_log_disc_t * disc)107 nvme_log_disc_fields(const nvme_log_disc_t *disc)
108 {
109 return (disc->nld_fields);
110 }
111
112 nvme_log_disc_scope_t
nvme_log_disc_scopes(const nvme_log_disc_t * disc)113 nvme_log_disc_scopes(const nvme_log_disc_t *disc)
114 {
115 return (disc->nld_scope);
116 }
117
118 bool
nvme_log_disc_impl(const nvme_log_disc_t * disc)119 nvme_log_disc_impl(const nvme_log_disc_t *disc)
120 {
121 return ((disc->nld_flags & NVME_LOG_DISC_F_IMPL) != 0);
122 }
123
124 nvme_log_size_kind_t
nvme_log_disc_size(const nvme_log_disc_t * disc,uint64_t * sizep)125 nvme_log_disc_size(const nvme_log_disc_t *disc, uint64_t *sizep)
126 {
127 *sizep = disc->nld_alloc_len;
128 return (disc->nld_size_kind);
129 }
130
131 /*
132 * For a variable length log page, presuming we've been given sufficient data
133 * actually determine the overall length that should now be used to get all
134 * data in the log.
135 */
136 bool
nvme_log_disc_calc_size(const nvme_log_disc_t * disc,uint64_t * act,const void * buf,size_t buflen)137 nvme_log_disc_calc_size(const nvme_log_disc_t *disc, uint64_t *act,
138 const void *buf, size_t buflen)
139 {
140 if (disc->nld_var_func == NULL) {
141 *act = disc->nld_alloc_len;
142 } else if (!disc->nld_var_func(act, buf, buflen)) {
143 return (false);
144 }
145
146 uint64_t orig = *act;
147
148 /*
149 * See nvme_log_page_info_size() in common/nvme/nvme_log.c for
150 * more context. Effectively NVMe log pages are required to be
151 * 4-byte aligned and we choose to round up.
152 */
153 *act = P2ROUNDUP(orig, NVME_DWORD_SIZE);
154 VERIFY3U(*act, >=, orig);
155 return (true);
156 }
157
158 bool
nvme_log_disc_dup(nvme_ctrl_t * ctrl,const nvme_log_disc_t * src,nvme_log_disc_t ** discp)159 nvme_log_disc_dup(nvme_ctrl_t *ctrl, const nvme_log_disc_t *src,
160 nvme_log_disc_t **discp)
161 {
162 nvme_log_disc_t *disc;
163
164 if (src == NULL) {
165 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
166 "encountered invalid nvme_log_disc_t pointer to duplicate: "
167 "%p", src));
168 }
169
170 if (discp == NULL) {
171 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
172 "encountered invalid nvme_log_disc_t output pointer: %p",
173 discp));
174 }
175
176 disc = calloc(1, sizeof (nvme_log_disc_t));
177 if (disc == NULL) {
178 int e = errno;
179 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
180 "allocate memory for a new nvme_log_disc_t: %s",
181 strerror(e)));
182 }
183
184 (void) memcpy(disc, src, sizeof (nvme_log_disc_t));
185 *discp = disc;
186 return (nvme_ctrl_success(ctrl));
187 }
188
189 /*
190 * Log Page Discovery logic
191 */
192 static bool
nvme_log_discover_validate(nvme_ctrl_t * ctrl,nvme_log_disc_scope_t scopes,uint32_t flags)193 nvme_log_discover_validate(nvme_ctrl_t *ctrl, nvme_log_disc_scope_t scopes,
194 uint32_t flags)
195 {
196 const nvme_log_disc_scope_t valid_scopes = NVME_LOG_SCOPE_CTRL |
197 NVME_LOG_SCOPE_NVM | NVME_LOG_SCOPE_NS;
198
199 /*
200 * For now require an explicit scope. Perhaps 0 should be an alias for
201 * allow all. That means if something gets added no one has to update to
202 * get new things, but on the other hand that means they might see
203 * unexpected scopes.
204 */
205 if (scopes == 0) {
206 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0, "no log "
207 "scope specified (given 0), a scope must be requested"));
208 }
209
210 if ((scopes & ~valid_scopes) != 0) {
211 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0,
212 "encountered invalid scope for the nvme_log_disc_scope_t: "
213 "0x%x", scopes & ~valid_scopes));
214 }
215
216 if (flags != 0) {
217 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0,
218 "encountered invalid log discovery flags: 0x%x", flags));
219 }
220
221 return (true);
222 }
223
224 /*
225 * The NVMe 2.0 specification adds a mandatory log page that describes which log
226 * pages are actually implemented, though not as much information as we use in
227 * discovery. We will attempt once per controller handle to get this log page
228 * and use it to augment the supported information in the discovery process.
229 * This log page is the first entry of the nvme_std_log_pages array.
230 */
231 static void
nvme_log_discover_fetch_sup_logs(nvme_ctrl_t * ctrl)232 nvme_log_discover_fetch_sup_logs(nvme_ctrl_t *ctrl)
233 {
234 const nvme_log_page_info_t *sup_info = &nvme_std_log_pages[0];
235 nvme_suplog_log_t *sup = NULL;
236 nvme_log_req_t *req = NULL;
237 nvme_valid_ctrl_data_t data;
238
239 VERIFY3U(sup_info->nlpi_lid, ==, NVME_LOGPAGE_SUP);
240
241 /*
242 * Mark the data in the nvme_ctrl_t as valid at this point. If this
243 * fails, we swallow this error and just will have slightly less
244 * information available on certain log pages. In general, this only
245 * impacts the detection of vendor defined log pages where our built-in
246 * information is not as accurate due to the lack of firmware
247 * information.
248 */
249 ctrl->nc_sup_logs = NULL;
250 ctrl->nc_flags |= NVME_CTRL_F_SUP_LOGS_VALID;
251
252 data.vcd_vers = &ctrl->nc_vers;
253 data.vcd_id = &ctrl->nc_info;
254
255 if (!nvme_log_page_info_supported(sup_info, &data)) {
256 return;
257 }
258
259 sup = calloc(1, sizeof (nvme_suplog_log_t));
260 if (sup == NULL) {
261 int e = errno;
262 (void) nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
263 "allocate memory for internal log page info: %s",
264 strerror(e));
265 goto err;
266 }
267
268 if (!nvme_log_req_init(ctrl, &req)) {
269 goto err;
270 }
271
272 if (!nvme_log_req_set_lid(req, sup_info->nlpi_lid) ||
273 !nvme_log_req_set_csi(req, sup_info->nlpi_csi) ||
274 !nvme_log_req_set_output(req, sup, sizeof (nvme_suplog_log_t)) ||
275 !nvme_log_req_exec(req)) {
276 goto err;
277 }
278
279 ctrl->nc_sup_logs = sup;
280 nvme_log_req_fini(req);
281 return;
282
283 err:
284 /*
285 * Flag this as failed and attempt to save the error that got us here.
286 * If a memory allocation failure occurs here, then there's not much
287 * more we can do, but at least this flag set and no error information
288 * does indicate a memory problem. Regardless, we clear out the error
289 * from the nvme_ctrl_t.
290 */
291 ctrl->nc_flags |= NVME_CTRL_F_SUP_LOGS_FAILED;
292 if ((ctrl->nc_sup_logs_err = calloc(1, sizeof (nvme_err_data_t))) !=
293 NULL) {
294 nvme_ctrl_err_save(ctrl, ctrl->nc_sup_logs_err);
295 }
296 (void) nvme_ctrl_success(ctrl);
297 nvme_log_req_fini(req);
298 free(sup);
299 }
300
301 void
nvme_log_discover_fini(nvme_log_iter_t * iter)302 nvme_log_discover_fini(nvme_log_iter_t *iter)
303 {
304 free(iter);
305 }
306
307 static bool
nvme_log_discover_one(nvme_log_iter_t * iter,const nvme_log_page_info_t * info)308 nvme_log_discover_one(nvme_log_iter_t *iter, const nvme_log_page_info_t *info)
309 {
310 bool var;
311 nvme_log_disc_t *disc = &iter->nli_nld;
312 nvme_ctrl_t *ctrl = iter->nli_ctrl;
313 nvme_log_disc_scope_t scope;
314 nvme_valid_ctrl_data_t data;
315
316 data.vcd_vers = &iter->nli_ctrl->nc_vers;
317 data.vcd_id = &iter->nli_ctrl->nc_info;
318
319 /*
320 * Determine the scope of the log page so we can understand if the user
321 * cares about this or not.
322 */
323 scope = nvme_log_page_info_scope(info, &data);
324 if ((iter->nli_scope & scope) == 0) {
325 return (false);
326 }
327
328 (void) memset(disc, 0, sizeof (nvme_log_disc_t));
329
330 /*
331 * Now that we know that this applies, fill in the remaining information
332 * that we need.
333 */
334 disc->nld_short = info->nlpi_short;
335 disc->nld_desc = info->nlpi_human;
336 disc->nld_aliases = info->nlpi_aliases;
337 disc->nld_naliases = info->nlpi_naliases;
338 disc->nld_lid = info->nlpi_lid;
339 disc->nld_csi = info->nlpi_csi;
340 disc->nld_kind = info->nlpi_kind;
341 disc->nld_srcs = info->nlpi_source;
342 disc->nld_scope = scope;
343 disc->nld_fields = info->nlpi_disc;
344
345 disc->nld_alloc_len = nvme_log_page_info_size(info, &data, &var);
346 if (disc->nld_alloc_len != 0) {
347 if (var) {
348 disc->nld_var_func = info->nlpi_var_func;
349 disc->nld_size_kind = NVME_LOG_SIZE_K_VAR;
350 } else {
351 disc->nld_size_kind = NVME_LOG_SIZE_K_FIXED;
352 }
353 } else {
354 disc->nld_size_kind = NVME_LOG_SIZE_K_UNKNOWN;
355 disc->nld_alloc_len = NVME_LOG_MAX_SIZE;
356 }
357
358 /*
359 * Determine if a log page is supported. This uses the per-log knowledge
360 * built into the nvme_log_page_info_t structures by default. When we
361 * have the NVMe 2.0 Supported Log Pages log, then we require both that
362 * and the internal bits fire. We've encountered some cases where the
363 * datasheet indicates something is supported, but firmware does not for
364 * some surprising reason. We haven't yet found cases where our logic
365 * says something is implemented but the log page information is wrong.
366 * That will likely come some day and we'll need a quirks list.
367 */
368 if (nvme_log_page_info_supported(info, &data) &&
369 (ctrl->nc_sup_logs == NULL ||
370 ctrl->nc_sup_logs->nl_logs[info->nlpi_lid].ns_lsupp) != 0) {
371 disc->nld_flags |= NVME_LOG_DISC_F_IMPL;
372 }
373
374 return (true);
375 }
376
377 nvme_iter_t
nvme_log_discover_step(nvme_log_iter_t * iter,const nvme_log_disc_t ** outp)378 nvme_log_discover_step(nvme_log_iter_t *iter, const nvme_log_disc_t **outp)
379 {
380 *outp = NULL;
381 nvme_ctrl_t *ctrl = iter->nli_ctrl;
382
383 if (iter->nli_std_done && iter->nli_vs_done) {
384 return (NVME_ITER_DONE);
385 }
386
387 /*
388 * We start by walking the list of spec pages and then check the device
389 * specific ones. While we may have the NVMe 2.0 Supported Log Page
390 * information, we don't really use that in discovery right now as it's
391 * rather hard to communicate useful discovery information with that. We
392 * mostly use this as a check on whether or not the log page is actually
393 * implemented based on our knowledge.
394 */
395 if (!iter->nli_std_done) {
396 while (iter->nli_cur_idx < nvme_std_log_npages) {
397 const nvme_log_page_info_t *info =
398 &nvme_std_log_pages[iter->nli_cur_idx];
399 iter->nli_cur_idx++;
400 if (nvme_log_discover_one(iter, info)) {
401 *outp = &iter->nli_nld;
402 return (NVME_ITER_VALID);
403 }
404 }
405 iter->nli_std_done = true;
406 iter->nli_cur_idx = 0;
407 }
408
409 if (ctrl->nc_vsd == NULL) {
410 iter->nli_vs_done = true;
411 return (NVME_ITER_DONE);
412 }
413
414 while (iter->nli_cur_idx < ctrl->nc_vsd->nvd_nlogs) {
415 const nvme_log_page_info_t *info =
416 ctrl->nc_vsd->nvd_logs[iter->nli_cur_idx];
417 iter->nli_cur_idx++;
418 if (nvme_log_discover_one(iter, info)) {
419 *outp = &iter->nli_nld;
420 return (NVME_ITER_VALID);
421 }
422 }
423
424 iter->nli_vs_done = true;
425 iter->nli_cur_idx = 0;
426 return (NVME_ITER_DONE);
427 }
428
429 bool
nvme_log_discover_init(nvme_ctrl_t * ctrl,nvme_log_disc_scope_t scopes,uint32_t flags,nvme_log_iter_t ** iterp)430 nvme_log_discover_init(nvme_ctrl_t *ctrl, nvme_log_disc_scope_t scopes,
431 uint32_t flags, nvme_log_iter_t **iterp)
432 {
433 nvme_log_iter_t *iter;
434
435 if (!nvme_log_discover_validate(ctrl, scopes, flags)) {
436 return (false);
437 }
438
439 if (iterp == NULL) {
440 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
441 "encountered invalid nvme_log_iter_t output pointer: %p",
442 iterp));
443 }
444
445 iter = calloc(1, sizeof (nvme_log_iter_t));
446 if (iter == NULL) {
447 int e = errno;
448 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
449 "allocate memory for a new nvme_log_iter_t: %s",
450 strerror(e)));
451 }
452
453 if ((ctrl->nc_flags & NVME_CTRL_F_SUP_LOGS_VALID) == 0) {
454 nvme_log_discover_fetch_sup_logs(ctrl);
455 }
456
457 iter->nli_ctrl = ctrl;
458 iter->nli_scope = scopes;
459
460 *iterp = iter;
461 return (nvme_ctrl_success(ctrl));
462 }
463
464 /*
465 * Walk all of the requested log pages that match and fill out the information
466 * for the discovery form.
467 */
468 bool
nvme_log_discover(nvme_ctrl_t * ctrl,nvme_log_disc_scope_t scopes,uint32_t flags,nvme_log_disc_f func,void * arg)469 nvme_log_discover(nvme_ctrl_t *ctrl, nvme_log_disc_scope_t scopes,
470 uint32_t flags, nvme_log_disc_f func, void *arg)
471 {
472 nvme_log_iter_t *iter;
473 nvme_iter_t ret;
474 const nvme_log_disc_t *disc;
475
476 if (func == NULL) {
477 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
478 "encountered invalid nvme_log_disc_f function pointer: %p",
479 func));
480 }
481
482 if (!nvme_log_discover_init(ctrl, scopes, flags, &iter)) {
483 return (false);
484 }
485
486 while ((ret = nvme_log_discover_step(iter, &disc)) == NVME_ITER_VALID) {
487 if (!func(ctrl, disc, arg))
488 break;
489 }
490
491 nvme_log_discover_fini(iter);
492 if (ret == NVME_ITER_ERROR) {
493 return (false);
494 }
495
496 return (nvme_ctrl_success(ctrl));
497 }
498
499
500 void
nvme_log_req_fini(nvme_log_req_t * req)501 nvme_log_req_fini(nvme_log_req_t *req)
502 {
503 free(req);
504 }
505
506 /*
507 * This is the totally manual path that occurs. When this is used, we require
508 * that people specify a subset of the fields here, primarily just the actual
509 * log page, output, and CSI. We don't try to be clever here and use the
510 * discovery information to know what to set. That's reserved for creating this
511 * request based upon discovery information.
512 */
513 bool
nvme_log_req_init(nvme_ctrl_t * ctrl,nvme_log_req_t ** reqp)514 nvme_log_req_init(nvme_ctrl_t *ctrl, nvme_log_req_t **reqp)
515 {
516 nvme_log_req_t *req;
517
518 if (reqp == NULL) {
519 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
520 "encountered invalid nvme_log_req_t output pointer: %p",
521 reqp));
522 }
523
524 req = calloc(1, sizeof (nvme_log_req_t));
525 if (req == NULL) {
526 int e = errno;
527 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
528 "allocate memory for a new nvme_log_req_t: %s",
529 strerror(e)));
530 }
531
532 req->nlr_ctrl = ctrl;
533 for (size_t i = 0; i < nvme_log_nfields; i++) {
534 if (nvme_log_fields[i].nlfi_def_req) {
535 req->nlr_need |= 1 << i;
536 }
537
538 if (nvme_log_fields[i].nlfi_def_allow) {
539 req->nlr_allow |= 1 << i;
540 }
541 }
542
543 /*
544 * Because we don't know anything about this log request, indicate that
545 * if we're given the all namespaces nsid that's fine. We'll still
546 * check the controller version when this is set first.
547 */
548 req->nlr_flags |= NVME_LOG_REQ_F_BCAST_NS_OK;
549
550 *reqp = req;
551 return (nvme_ctrl_success(ctrl));
552 }
553
554 bool
nvme_log_req_init_by_disc(nvme_ctrl_t * ctrl,const nvme_log_disc_t * disc,nvme_log_req_t ** reqp)555 nvme_log_req_init_by_disc(nvme_ctrl_t *ctrl, const nvme_log_disc_t *disc,
556 nvme_log_req_t **reqp)
557 {
558 nvme_log_req_t *req;
559
560 if (disc == NULL) {
561 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
562 "encountered invalid nvme_log_disc_t pointer: %p", disc));
563 }
564
565 if (reqp == NULL) {
566 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
567 "encountered invalid nvme_log_req_t output pointer: %p",
568 reqp));
569 }
570
571 if ((disc->nld_flags & NVME_LOG_DISC_F_IMPL) == 0) {
572 return (nvme_ctrl_error(ctrl, NVME_ERR_LOG_UNSUP_BY_DEV, 0,
573 "cannot create log request for log %s (CSI/LID 0x%x/0x%x) "
574 "because it is not supported by the device",
575 disc->nld_short, disc->nld_csi, disc->nld_lid));
576 }
577
578 req = calloc(1, sizeof (nvme_log_req_t));
579 if (req == NULL) {
580 int e = errno;
581 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
582 "allocate memory for a new nvme_log_req_t: %s",
583 strerror(e)));
584 }
585
586 req->nlr_ctrl = ctrl;
587 req->nlr_lid = disc->nld_lid;
588 req->nlr_csi = disc->nld_csi;
589
590 /*
591 * Setting the size is always required here, because this is how we
592 * track that the output pointer is actually set. We will always allow
593 * setting the offset though it's possible the controller won't support
594 * that.
595 */
596 req->nlr_need = req->nlr_allow = 1 << NVME_LOG_REQ_FIELD_SIZE;
597 req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_OFFSET;
598
599 /*
600 * Initialize our needed and allowed fields. Because we have the actual
601 * lid/csi from the above, we don't allow the user to overwrite them at
602 * all. For the LSP and LSI, right now these are all our nothing, but
603 * this may break. RAE is a bit special and discussed below.
604 */
605 if ((disc->nld_fields & NVME_LOG_DISC_F_NEED_LSP) != 0) {
606 req->nlr_need |= 1 << NVME_LOG_REQ_FIELD_LSP;
607 req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_LSP;
608 }
609
610 if ((disc->nld_fields & NVME_LOG_DISC_F_NEED_LSI) != 0) {
611 req->nlr_need |= 1 << NVME_LOG_REQ_FIELD_LSI;
612 req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_LSI;
613 }
614
615 /*
616 * Because RAE wasn't added until NVMe 1.3, we can't do much with it
617 * before that. However, once it's here we definitely want to default to
618 * setting it by default so that way we can minimize the chance that
619 * we'll steal an alert that the kernel needs to read and acknowledge.
620 */
621 if ((disc->nld_fields & NVME_LOG_DISC_F_NEED_RAE) != 0 &&
622 nvme_vers_ctrl_atleast(ctrl,
623 nvme_log_fields[NVME_LOG_REQ_FIELD_RAE].nlfi_vers)) {
624 req->nlr_flags |= NVME_LOG_REQ_F_RAE;
625 req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_RAE;
626 }
627
628 /*
629 * Check the log page scope setting. If the log is said to be namespace
630 * scoped, then we'll allow the namespace to be specified. If it
631 * supports a different scope as well, then we'll default to the
632 * controller scope and this field is optional. Otherwise, it'll be
633 * required and it will be a mandatory field.
634 */
635 if ((disc->nld_scope & NVME_LOG_SCOPE_NS) != 0) {
636 req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_NSID;
637 if ((disc->nld_scope & ~NVME_LOG_SCOPE_NS) != 0) {
638 req->nlr_flags |= NVME_LOG_REQ_F_BCAST_NS_OK;
639 req->nlr_nsid = NVME_NSID_BCAST;
640 } else {
641 req->nlr_need |= 1 << NVME_LOG_REQ_FIELD_NSID;
642 }
643 }
644
645 *reqp = req;
646 return (nvme_ctrl_success(ctrl));
647 }
648
649 typedef struct {
650 bool nlia_found;
651 const char *nlia_name;
652 nvme_log_req_t *nlia_req;
653 nvme_log_disc_t **nlia_discp;
654 nvme_err_data_t nlia_err;
655 } nvme_log_init_arg_t;
656
657 static bool
nvme_log_req_match_disc(const char * name,const nvme_log_disc_t * disc)658 nvme_log_req_match_disc(const char *name, const nvme_log_disc_t *disc)
659 {
660 if (strcmp(name, disc->nld_short) == 0) {
661 return (true);
662 }
663
664 for (size_t i = 0; i < disc->nld_naliases; i++) {
665 if (strcmp(name, disc->nld_aliases[i]) == 0) {
666 return (true);
667 }
668 }
669
670 return (false);
671 }
672
673 static bool
nvme_log_req_init_by_name_cb(nvme_ctrl_t * ctrl,const nvme_log_disc_t * disc,void * arg)674 nvme_log_req_init_by_name_cb(nvme_ctrl_t *ctrl, const nvme_log_disc_t *disc,
675 void *arg)
676 {
677 nvme_log_init_arg_t *init = arg;
678
679 if (!nvme_log_req_match_disc(init->nlia_name, disc)) {
680 return (true);
681 }
682
683 init->nlia_found = true;
684 if (!nvme_log_req_init_by_disc(ctrl, disc, &init->nlia_req)) {
685 nvme_ctrl_err_save(ctrl, &init->nlia_err);
686 init->nlia_req = NULL;
687 } else if (init->nlia_discp != NULL) {
688 if (!nvme_log_disc_dup(ctrl, disc, init->nlia_discp)) {
689 nvme_ctrl_err_save(ctrl, &init->nlia_err);
690 nvme_log_req_fini(init->nlia_req);
691 init->nlia_req = NULL;
692 }
693 }
694
695 return (false);
696 }
697
698 bool
nvme_log_req_init_by_name(nvme_ctrl_t * ctrl,const char * name,uint32_t flags,nvme_log_disc_t ** discp,nvme_log_req_t ** reqp)699 nvme_log_req_init_by_name(nvme_ctrl_t *ctrl, const char *name, uint32_t flags,
700 nvme_log_disc_t **discp, nvme_log_req_t **reqp)
701 {
702 nvme_log_init_arg_t init;
703
704 /*
705 * We consider discp an optional argument and therefore do not check it
706 * unlike name and reqp.
707 */
708 if (reqp == NULL) {
709 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
710 "encountered invalid nvme_log_req_t output pointer: %p",
711 reqp));
712 }
713
714 if (name == NULL) {
715 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
716 "encountered invalid pointer for log page name: %p", name));
717 }
718
719 (void) memset(&init, 0, sizeof (init));
720 init.nlia_name = name;
721 init.nlia_discp = discp;
722
723 if (!nvme_log_discover(ctrl, NVME_LOG_SCOPE_CTRL |
724 NVME_LOG_SCOPE_NVM | NVME_LOG_SCOPE_NS, flags,
725 nvme_log_req_init_by_name_cb, &init)) {
726 return (false);
727 }
728
729 if (!init.nlia_found) {
730 return (nvme_ctrl_error(ctrl, NVME_ERR_LOG_NAME_UNKNOWN, 0,
731 "failed to find log page with name %s", name));
732 }
733
734 /*
735 * If we failed to create the request, but we did find it, then that
736 * means something went wrong and we can go ahead and already return an
737 * error.
738 */
739 if (init.nlia_req == NULL) {
740 nvme_ctrl_err_set(ctrl, &init.nlia_err);
741 return (false);
742 }
743
744 *reqp = init.nlia_req;
745 return (nvme_ctrl_success(ctrl));
746 }
747
748 static void
nvme_log_req_set_need(nvme_log_req_t * req,nvme_log_req_field_t field)749 nvme_log_req_set_need(nvme_log_req_t *req, nvme_log_req_field_t field)
750 {
751 req->nlr_need |= 1 << field;
752 }
753
754 static void
nvme_log_req_clear_need(nvme_log_req_t * req,nvme_log_req_field_t field)755 nvme_log_req_clear_need(nvme_log_req_t *req, nvme_log_req_field_t field)
756 {
757 req->nlr_need &= ~(1 << field);
758 }
759
760 static const nvme_field_check_t nvme_log_check_lid = {
761 nvme_log_fields, NVME_LOG_REQ_FIELD_LID,
762 NVME_ERR_LOG_LID_RANGE, 0, 0
763 };
764
765 bool
nvme_log_req_set_lid(nvme_log_req_t * req,uint32_t lid)766 nvme_log_req_set_lid(nvme_log_req_t *req, uint32_t lid)
767 {
768 if (!nvme_field_check_one(req->nlr_ctrl, lid, "get log page",
769 &nvme_log_check_lid, req->nlr_allow)) {
770 return (false);
771 }
772
773 req->nlr_lid = lid;
774 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_LID);
775 return (nvme_ctrl_success(req->nlr_ctrl));
776 }
777
778 static const nvme_field_check_t nvme_log_check_lsp = {
779 nvme_log_fields, NVME_LOG_REQ_FIELD_LSP,
780 NVME_ERR_LOG_LSP_RANGE, NVME_ERR_LOG_LSP_UNSUP,
781 NVME_ERR_LOG_LSP_UNUSE
782 };
783
784 bool
nvme_log_req_set_lsp(nvme_log_req_t * req,uint32_t lsp)785 nvme_log_req_set_lsp(nvme_log_req_t *req, uint32_t lsp)
786 {
787 if (!nvme_field_check_one(req->nlr_ctrl, lsp, "get log page",
788 &nvme_log_check_lsp, req->nlr_allow)) {
789 return (false);
790 }
791
792 req->nlr_lsp = lsp;
793 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_LSP);
794 return (nvme_ctrl_success(req->nlr_ctrl));
795 }
796
797 static const nvme_field_check_t nvme_log_check_lsi = {
798 nvme_log_fields, NVME_LOG_REQ_FIELD_LSI,
799 NVME_ERR_LOG_LSI_RANGE, NVME_ERR_LOG_LSI_UNSUP,
800 NVME_ERR_LOG_LSI_UNUSE
801 };
802
803 bool
nvme_log_req_set_lsi(nvme_log_req_t * req,uint32_t lsi)804 nvme_log_req_set_lsi(nvme_log_req_t *req, uint32_t lsi)
805 {
806 if (!nvme_field_check_one(req->nlr_ctrl, lsi, "get log page",
807 &nvme_log_check_lsi, req->nlr_allow)) {
808 return (false);
809 }
810
811 req->nlr_lsi = lsi;
812 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_LSI);
813 return (nvme_ctrl_success(req->nlr_ctrl));
814 }
815
816 static const nvme_field_check_t nvme_log_check_csi = {
817 nvme_log_fields, NVME_LOG_REQ_FIELD_CSI,
818 NVME_ERR_LOG_CSI_RANGE, NVME_ERR_LOG_CSI_UNSUP, 0
819 };
820
821 bool
nvme_log_req_set_csi(nvme_log_req_t * req,nvme_csi_t csi)822 nvme_log_req_set_csi(nvme_log_req_t *req, nvme_csi_t csi)
823 {
824 if (!nvme_field_check_one(req->nlr_ctrl, csi, "get log page",
825 &nvme_log_check_csi, req->nlr_allow)) {
826 return (false);
827 }
828
829 req->nlr_csi = csi;
830 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_CSI);
831 return (nvme_ctrl_success(req->nlr_ctrl));
832 }
833
834 static const nvme_field_check_t nvme_log_check_size = {
835 nvme_log_fields, NVME_LOG_REQ_FIELD_SIZE,
836 NVME_ERR_LOG_SIZE_RANGE, 0, 0
837 };
838
839 bool
nvme_log_req_set_output(nvme_log_req_t * req,void * buf,size_t buflen)840 nvme_log_req_set_output(nvme_log_req_t *req, void *buf, size_t buflen)
841 {
842 if (buf == NULL) {
843 return (nvme_ctrl_error(req->nlr_ctrl, NVME_ERR_BAD_PTR, 0,
844 "log request output buffer cannot be NULL"));
845 }
846
847 if (!nvme_field_check_one(req->nlr_ctrl, buflen, "get log page",
848 &nvme_log_check_size, req->nlr_allow)) {
849 return (false);
850 }
851
852 req->nlr_output = buf;
853 req->nlr_output_len = buflen;
854 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_SIZE);
855 return (nvme_ctrl_success(req->nlr_ctrl));
856 }
857
858 bool
nvme_log_req_clear_output(nvme_log_req_t * req)859 nvme_log_req_clear_output(nvme_log_req_t *req)
860 {
861 req->nlr_output = NULL;
862 req->nlr_output_len = 0;
863
864 /*
865 * We can always set that we need this again as every log page requires
866 * a size being set. See the default allow settings for the field in
867 * nvme_log_fields[] and nvme_log_req_init_by_disc().
868 */
869 nvme_log_req_set_need(req, NVME_LOG_REQ_FIELD_SIZE);
870 return (nvme_ctrl_success(req->nlr_ctrl));
871 }
872
873 static const nvme_field_check_t nvme_log_check_offset = {
874 nvme_log_fields, NVME_LOG_REQ_FIELD_OFFSET,
875 NVME_ERR_LOG_OFFSET_RANGE, 0, 0
876 };
877
878 bool
nvme_log_req_set_offset(nvme_log_req_t * req,uint64_t off)879 nvme_log_req_set_offset(nvme_log_req_t *req, uint64_t off)
880 {
881 if (!nvme_field_check_one(req->nlr_ctrl, off, "get log page",
882 &nvme_log_check_offset, req->nlr_allow)) {
883 return (false);
884 }
885
886 req->nlr_offset = off;
887 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_OFFSET);
888 return (nvme_ctrl_success(req->nlr_ctrl));
889 }
890
891 static const nvme_field_check_t nvme_log_check_nsid = {
892 nvme_log_fields, NVME_LOG_REQ_FIELD_NSID, NVME_ERR_NS_RANGE, 0, 0
893 };
894
895 bool
nvme_log_req_set_nsid(nvme_log_req_t * req,uint32_t nsid)896 nvme_log_req_set_nsid(nvme_log_req_t *req, uint32_t nsid)
897 {
898 nvme_ctrl_t *ctrl = req->nlr_ctrl;
899
900 if (nsid == NVME_NSID_BCAST &&
901 (req->nlr_flags & NVME_LOG_REQ_F_BCAST_NS_OK) == 0) {
902 return (nvme_ctrl_error(ctrl, NVME_ERR_NS_RANGE, 0, "the all "
903 "namespaces/controller nsid (0x%x) is not allowed for this "
904 "log page, valid namespaces are [0x%x, 0x%x]", nsid,
905 NVME_NSID_MIN, req->nlr_ctrl->nc_info.id_nn));
906 }
907
908 if (!nvme_field_check_one(req->nlr_ctrl, nsid, "get log page",
909 &nvme_log_check_nsid, req->nlr_allow)) {
910 return (false);
911 }
912
913 req->nlr_nsid = nsid;
914 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_NSID);
915 return (nvme_ctrl_success(req->nlr_ctrl));
916 }
917
918 static const nvme_field_check_t nvme_log_check_rae = {
919 nvme_log_fields, NVME_LOG_REQ_FIELD_RAE,
920 NVME_ERR_LOG_RAE_RANGE, NVME_ERR_LOG_RAE_UNSUP,
921 NVME_ERR_LOG_RAE_UNUSE
922 };
923
924 bool
nvme_log_req_set_rae(nvme_log_req_t * req,bool rae)925 nvme_log_req_set_rae(nvme_log_req_t *req, bool rae)
926 {
927 if (!nvme_field_check_one(req->nlr_ctrl, rae, "get log page",
928 &nvme_log_check_rae, req->nlr_allow)) {
929 return (false);
930 }
931
932 if (rae) {
933 req->nlr_flags |= NVME_LOG_REQ_F_RAE;
934 } else {
935 req->nlr_flags &= ~NVME_LOG_REQ_F_RAE;
936 }
937 nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_RAE);
938 return (nvme_ctrl_success(req->nlr_ctrl));
939 }
940
941 bool
nvme_log_req_exec(nvme_log_req_t * req)942 nvme_log_req_exec(nvme_log_req_t *req)
943 {
944 nvme_ctrl_t *ctrl = req->nlr_ctrl;
945 nvme_ioctl_get_logpage_t log;
946
947 if (req->nlr_need != 0) {
948 return (nvme_field_miss_err(ctrl, nvme_log_fields,
949 nvme_log_nfields, NVME_ERR_LOG_REQ_MISSING_FIELDS,
950 "get log page", req->nlr_need));
951 }
952
953 (void) memset(&log, 0, sizeof (nvme_ioctl_get_logpage_t));
954 log.nigl_common.nioc_nsid = req->nlr_nsid;
955 log.nigl_csi = req->nlr_csi;
956 log.nigl_lid = req->nlr_lid;
957 log.nigl_lsp = req->nlr_lsp;
958 log.nigl_lsi = req->nlr_lsi;
959 if ((req->nlr_flags & NVME_LOG_REQ_F_RAE) != 0) {
960 log.nigl_rae = 1;
961 }
962 log.nigl_len = req->nlr_output_len;
963 log.nigl_offset = req->nlr_offset;
964 log.nigl_data = (uintptr_t)req->nlr_output;
965
966 if (ioctl(ctrl->nc_fd, NVME_IOC_GET_LOGPAGE, &log) != 0) {
967 int e = errno;
968 return (nvme_ioctl_syserror(ctrl, e, "get log page"));
969 }
970
971 if (log.nigl_common.nioc_drv_err != NVME_IOCTL_E_OK) {
972 return (nvme_ioctl_error(ctrl, &log.nigl_common,
973 "get log page"));
974 }
975
976 return (nvme_ctrl_success(ctrl));
977 }
978