xref: /illumos-gate/usr/src/lib/libnvme/common/libnvme_log.c (revision ffd3f1847d39a0d148aa934d9051f453189adfe2)
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