xref: /illumos-gate/usr/src/lib/libnvme/common/libnvme_log.c (revision 1c02c6c85edfeb48df1fe18511a8779bf9d6c6ed)
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 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 nvme_csi_t
nvme_log_disc_csi(const nvme_log_disc_t * disc)71 nvme_log_disc_csi(const nvme_log_disc_t *disc)
72 {
73 	return (disc->nld_csi);
74 }
75 
76 uint32_t
nvme_log_disc_lid(const nvme_log_disc_t * disc)77 nvme_log_disc_lid(const nvme_log_disc_t *disc)
78 {
79 	return (disc->nld_lid);
80 }
81 
82 nvme_log_disc_kind_t
nvme_log_disc_kind(const nvme_log_disc_t * disc)83 nvme_log_disc_kind(const nvme_log_disc_t *disc)
84 {
85 	return (disc->nld_kind);
86 }
87 
88 nvme_log_disc_source_t
nvme_log_disc_sources(const nvme_log_disc_t * disc)89 nvme_log_disc_sources(const nvme_log_disc_t *disc)
90 {
91 	return (disc->nld_srcs);
92 }
93 
94 nvme_log_disc_fields_t
nvme_log_disc_fields(const nvme_log_disc_t * disc)95 nvme_log_disc_fields(const nvme_log_disc_t *disc)
96 {
97 	return (disc->nld_fields);
98 }
99 
100 nvme_log_disc_scope_t
nvme_log_disc_scopes(const nvme_log_disc_t * disc)101 nvme_log_disc_scopes(const nvme_log_disc_t *disc)
102 {
103 	return (disc->nld_scope);
104 }
105 
106 bool
nvme_log_disc_impl(const nvme_log_disc_t * disc)107 nvme_log_disc_impl(const nvme_log_disc_t *disc)
108 {
109 	return ((disc->nld_flags & NVME_LOG_DISC_F_IMPL) != 0);
110 }
111 
112 nvme_log_size_kind_t
nvme_log_disc_size(const nvme_log_disc_t * disc,uint64_t * sizep)113 nvme_log_disc_size(const nvme_log_disc_t *disc, uint64_t *sizep)
114 {
115 	*sizep = disc->nld_alloc_len;
116 	return (disc->nld_size_kind);
117 }
118 
119 /*
120  * For a variable length log page, presuming we've been given sufficient data
121  * actually determine the overall length that should now be used to get all
122  * data in the log.
123  */
124 bool
nvme_log_disc_calc_size(const nvme_log_disc_t * disc,uint64_t * act,const void * buf,size_t buflen)125 nvme_log_disc_calc_size(const nvme_log_disc_t *disc, uint64_t *act,
126     const void *buf, size_t buflen)
127 {
128 	if (disc->nld_var_func == NULL) {
129 		*act = disc->nld_alloc_len;
130 	}
131 
132 	return (disc->nld_var_func(act, buf, buflen));
133 }
134 
135 bool
nvme_log_disc_dup(nvme_ctrl_t * ctrl,const nvme_log_disc_t * src,nvme_log_disc_t ** discp)136 nvme_log_disc_dup(nvme_ctrl_t *ctrl, const nvme_log_disc_t *src,
137     nvme_log_disc_t **discp)
138 {
139 	nvme_log_disc_t *disc;
140 
141 	if (src == NULL) {
142 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
143 		    "encountered invalid nvme_log_disc_t pointer to duplicate: "
144 		    "%p", src));
145 	}
146 
147 	if (discp == NULL) {
148 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
149 		    "encountered invalid nvme_log_disc_t output pointer: %p",
150 		    discp));
151 	}
152 
153 	disc = calloc(1, sizeof (nvme_log_disc_t));
154 	if (disc == NULL) {
155 		int e = errno;
156 		return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
157 		    "allocate memory for a new nvme_log_disc_t: %s",
158 		    strerror(e)));
159 	}
160 
161 	(void) memcpy(disc, src, sizeof (nvme_log_disc_t));
162 	*discp = disc;
163 	return (nvme_ctrl_success(ctrl));
164 }
165 
166 /*
167  * Log Page Discovery logic
168  */
169 static bool
nvme_log_discover_validate(nvme_ctrl_t * ctrl,nvme_log_disc_scope_t scopes,uint32_t flags)170 nvme_log_discover_validate(nvme_ctrl_t *ctrl, nvme_log_disc_scope_t scopes,
171     uint32_t flags)
172 {
173 	const nvme_log_disc_scope_t valid_scopes = NVME_LOG_SCOPE_CTRL |
174 	    NVME_LOG_SCOPE_NVM | NVME_LOG_SCOPE_NS;
175 
176 	/*
177 	 * For now require an explicit scope. Perhaps 0 should be an alias for
178 	 * allow all. That means if something gets added no one has to update to
179 	 * get new things, but on the other hand that means they might see
180 	 * unexpected scopes.
181 	 */
182 	if (scopes == 0) {
183 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0, "no log "
184 		    "scope specified (given 0), a scope must be requested"));
185 	}
186 
187 	if ((scopes & ~valid_scopes) != 0) {
188 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0,
189 		    "encountered invalid scope for the nvme_log_disc_scope_t: "
190 		    "0x%x", scopes & ~valid_scopes));
191 	}
192 
193 	if (flags != 0) {
194 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0,
195 		    "encountered invalid log discovery flags: 0x%x", flags));
196 	}
197 
198 	return (true);
199 }
200 
201 void
nvme_log_discover_fini(nvme_log_iter_t * iter)202 nvme_log_discover_fini(nvme_log_iter_t *iter)
203 {
204 	free(iter);
205 }
206 
207 static bool
nvme_log_discover_one(nvme_log_iter_t * iter,const nvme_log_page_info_t * info)208 nvme_log_discover_one(nvme_log_iter_t *iter, const nvme_log_page_info_t *info)
209 {
210 	bool var;
211 	nvme_log_disc_t *disc = &iter->nli_nld;
212 	nvme_log_disc_scope_t scope;
213 	nvme_valid_ctrl_data_t data;
214 
215 	data.vcd_vers = &iter->nli_ctrl->nc_vers;
216 	data.vcd_id = &iter->nli_ctrl->nc_info;
217 
218 	/*
219 	 * Determine the scope of the log page so we can understand if the user
220 	 * cares about this or not.
221 	 */
222 	scope = nvme_log_page_info_scope(info, &data);
223 	if ((iter->nli_scope & scope) == 0) {
224 		return (false);
225 	}
226 
227 	(void) memset(disc, 0, sizeof (nvme_log_disc_t));
228 
229 	/*
230 	 * Now that we know that this applies, fill in the remaining information
231 	 * that we need.
232 	 */
233 	disc->nld_short = info->nlpi_short;
234 	disc->nld_desc = info->nlpi_human;
235 	disc->nld_lid = info->nlpi_lid;
236 	disc->nld_csi = info->nlpi_csi;
237 	disc->nld_kind = info->nlpi_kind;
238 	disc->nld_srcs = info->nlpi_source;
239 	disc->nld_scope = scope;
240 	disc->nld_fields = info->nlpi_disc;
241 
242 	disc->nld_alloc_len = nvme_log_page_info_size(info, &data, &var);
243 	if (disc->nld_alloc_len != 0) {
244 		if (var) {
245 			disc->nld_var_func = info->nlpi_var_func;
246 			disc->nld_size_kind = NVME_LOG_SIZE_K_VAR;
247 		} else {
248 			disc->nld_size_kind = NVME_LOG_SIZE_K_FIXED;
249 		}
250 	} else {
251 		disc->nld_size_kind = NVME_LOG_SIZE_K_UNKNOWN;
252 		disc->nld_alloc_len = NVME_LOG_MAX_SIZE;
253 	}
254 
255 	if (nvme_log_page_info_supported(info, &data)) {
256 		disc->nld_flags |= NVME_LOG_DISC_F_IMPL;
257 	}
258 
259 	return (true);
260 }
261 
262 nvme_iter_t
nvme_log_discover_step(nvme_log_iter_t * iter,const nvme_log_disc_t ** outp)263 nvme_log_discover_step(nvme_log_iter_t *iter, const nvme_log_disc_t **outp)
264 {
265 	*outp = NULL;
266 	nvme_ctrl_t *ctrl = iter->nli_ctrl;
267 
268 	if (iter->nli_std_done && iter->nli_vs_done) {
269 		return (NVME_ITER_DONE);
270 	}
271 
272 	/*
273 	 * We start by walking the list of spec pages and then check the device
274 	 * specific ones. In the NVMe 2.x era or when we have support for a
275 	 * vendor-specific method for getting supported log pages then we should
276 	 * prefer executing that and using that info to provide information
277 	 * where possible.
278 	 */
279 	if (!iter->nli_std_done) {
280 		while (iter->nli_cur_idx < nvme_std_log_npages) {
281 			const nvme_log_page_info_t *info =
282 			    &nvme_std_log_pages[iter->nli_cur_idx];
283 			iter->nli_cur_idx++;
284 			if (nvme_log_discover_one(iter, info)) {
285 				*outp = &iter->nli_nld;
286 				return (NVME_ITER_VALID);
287 			}
288 		}
289 		iter->nli_std_done = true;
290 		iter->nli_cur_idx = 0;
291 	}
292 
293 	if (ctrl->nc_vsd == NULL) {
294 		iter->nli_vs_done = true;
295 		return (NVME_ITER_DONE);
296 	}
297 
298 	while (iter->nli_cur_idx < ctrl->nc_vsd->nvd_nlogs) {
299 		const nvme_log_page_info_t *info =
300 		    ctrl->nc_vsd->nvd_logs[iter->nli_cur_idx];
301 		iter->nli_cur_idx++;
302 		if (nvme_log_discover_one(iter, info)) {
303 			*outp = &iter->nli_nld;
304 			return (NVME_ITER_VALID);
305 		}
306 	}
307 
308 	iter->nli_vs_done = true;
309 	iter->nli_cur_idx = 0;
310 	return (NVME_ITER_DONE);
311 }
312 
313 bool
nvme_log_discover_init(nvme_ctrl_t * ctrl,nvme_log_disc_scope_t scopes,uint32_t flags,nvme_log_iter_t ** iterp)314 nvme_log_discover_init(nvme_ctrl_t *ctrl, nvme_log_disc_scope_t scopes,
315     uint32_t flags, nvme_log_iter_t **iterp)
316 {
317 	nvme_log_iter_t *iter;
318 
319 	if (!nvme_log_discover_validate(ctrl, scopes, flags)) {
320 		return (false);
321 	}
322 
323 	if (iterp == NULL) {
324 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
325 		    "encountered invalid nvme_log_iter_t output pointer: %p",
326 		    iterp));
327 	}
328 
329 	iter = calloc(1, sizeof (nvme_log_iter_t));
330 	if (iter == NULL) {
331 		int e = errno;
332 		return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
333 		    "allocate memory for a new nvme_log_iter_t: %s",
334 		    strerror(e)));
335 	}
336 
337 	iter->nli_ctrl = ctrl;
338 	iter->nli_scope = scopes;
339 
340 	*iterp = iter;
341 	return (nvme_ctrl_success(ctrl));
342 }
343 
344 /*
345  * Walk all of the requested log pages that match and fill out the information
346  * for the discovery form.
347  */
348 bool
nvme_log_discover(nvme_ctrl_t * ctrl,nvme_log_disc_scope_t scopes,uint32_t flags,nvme_log_disc_f func,void * arg)349 nvme_log_discover(nvme_ctrl_t *ctrl, nvme_log_disc_scope_t scopes,
350     uint32_t flags, nvme_log_disc_f func, void *arg)
351 {
352 	nvme_log_iter_t *iter;
353 	nvme_iter_t ret;
354 	const nvme_log_disc_t *disc;
355 
356 	if (func == NULL) {
357 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
358 		    "encountered invalid nvme_log_disc_f function pointer: %p",
359 		    func));
360 	}
361 
362 	if (!nvme_log_discover_init(ctrl, scopes, flags, &iter)) {
363 		return (false);
364 	}
365 
366 	while ((ret = nvme_log_discover_step(iter, &disc)) == NVME_ITER_VALID) {
367 		if (!func(ctrl, disc, arg))
368 			break;
369 	}
370 
371 	nvme_log_discover_fini(iter);
372 	if (ret == NVME_ITER_ERROR) {
373 		return (false);
374 	}
375 
376 	return (nvme_ctrl_success(ctrl));
377 }
378 
379 
380 void
nvme_log_req_fini(nvme_log_req_t * req)381 nvme_log_req_fini(nvme_log_req_t *req)
382 {
383 	free(req);
384 }
385 
386 /*
387  * This is the totally manual path that occurs. When this is used, we require
388  * that people specify a subset of the fields here, primarily just the actual
389  * log page, output, and CSI. We don't try to be clever here and use the
390  * discovery information to know what to set. That's reserved for creating this
391  * request based upon discovery information.
392  */
393 bool
nvme_log_req_init(nvme_ctrl_t * ctrl,nvme_log_req_t ** reqp)394 nvme_log_req_init(nvme_ctrl_t *ctrl, nvme_log_req_t **reqp)
395 {
396 	nvme_log_req_t *req;
397 
398 	if (reqp == NULL) {
399 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
400 		    "encountered invalid nvme_log_req_t output pointer: %p",
401 		    reqp));
402 	}
403 
404 	req = calloc(1, sizeof (nvme_log_req_t));
405 	if (req == NULL) {
406 		int e = errno;
407 		return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
408 		    "allocate memory for a new nvme_log_req_t: %s",
409 		    strerror(e)));
410 	}
411 
412 	req->nlr_ctrl = ctrl;
413 	for (size_t i = 0; i < nvme_log_nfields; i++) {
414 		if (nvme_log_fields[i].nlfi_def_req) {
415 			req->nlr_need |= 1 << i;
416 		}
417 
418 		if (nvme_log_fields[i].nlfi_def_allow) {
419 			req->nlr_allow |= 1 << i;
420 		}
421 	}
422 
423 	/*
424 	 * Because we don't know anything about this log request, indicate that
425 	 * if we're given the all namespaces nsid that's fine. We'll still
426 	 * check the controller version when this is set first.
427 	 */
428 	req->nlr_flags |= NVME_LOG_REQ_F_BCAST_NS_OK;
429 
430 	*reqp = req;
431 	return (nvme_ctrl_success(ctrl));
432 }
433 
434 bool
nvme_log_req_init_by_disc(nvme_ctrl_t * ctrl,const nvme_log_disc_t * disc,nvme_log_req_t ** reqp)435 nvme_log_req_init_by_disc(nvme_ctrl_t *ctrl, const nvme_log_disc_t *disc,
436     nvme_log_req_t **reqp)
437 {
438 	nvme_log_req_t *req;
439 
440 	if (disc == NULL) {
441 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
442 		    "encountered invalid nvme_log_disc_t pointer: %p", disc));
443 	}
444 
445 	if (reqp == NULL) {
446 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
447 		    "encountered invalid nvme_log_req_t output pointer: %p",
448 		    reqp));
449 	}
450 
451 	if ((disc->nld_flags & NVME_LOG_DISC_F_IMPL) == 0) {
452 		return (nvme_ctrl_error(ctrl, NVME_ERR_LOG_UNSUP_BY_DEV, 0,
453 		    "cannot create log request for log %s (CSI/LID 0x%x/0x%x) "
454 		    "because it is not supported by the device",
455 		    disc->nld_short, disc->nld_csi, disc->nld_lid));
456 	}
457 
458 	req = calloc(1, sizeof (nvme_log_req_t));
459 	if (req == NULL) {
460 		int e = errno;
461 		return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
462 		    "allocate memory for a new nvme_log_req_t: %s",
463 		    strerror(e)));
464 	}
465 
466 	req->nlr_ctrl = ctrl;
467 	req->nlr_lid = disc->nld_lid;
468 	req->nlr_csi = disc->nld_csi;
469 
470 	/*
471 	 * Setting the size is always required here, because this is how we
472 	 * track that the output pointer is actually set. We will always allow
473 	 * setting the offset though it's possible the controller won't support
474 	 * that.
475 	 */
476 	req->nlr_need = req->nlr_allow = 1 << NVME_LOG_REQ_FIELD_SIZE;
477 	req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_OFFSET;
478 
479 	/*
480 	 * Initialize our needed and allowed fields. Because we have the actual
481 	 * lid/csi from the above, we don't allow the user to overwrite them at
482 	 * all. For the LSP and LSI, right now these are all our nothing, but
483 	 * this may break. RAE is a bit special and discussed below.
484 	 */
485 	if ((disc->nld_fields & NVME_LOG_DISC_F_NEED_LSP) != 0) {
486 		req->nlr_need |= 1 << NVME_LOG_REQ_FIELD_LSP;
487 		req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_LSP;
488 	}
489 
490 	if ((disc->nld_fields & NVME_LOG_DISC_F_NEED_LSI) != 0) {
491 		req->nlr_need |= 1 << NVME_LOG_REQ_FIELD_LSI;
492 		req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_LSI;
493 	}
494 
495 	/*
496 	 * Because RAE wasn't added until NVMe 1.3, we can't do much with it
497 	 * before that. However, once it's here we definitely want to default to
498 	 * setting it by default so that way we can minimize the chance that
499 	 * we'll steal an alert that the kernel needs to read and acknowledge.
500 	 */
501 	if ((disc->nld_fields & NVME_LOG_DISC_F_NEED_RAE) != 0 &&
502 	    nvme_vers_ctrl_atleast(ctrl,
503 	    nvme_log_fields[NVME_LOG_REQ_FIELD_RAE].nlfi_vers)) {
504 		req->nlr_flags |= NVME_LOG_REQ_F_RAE;
505 		req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_RAE;
506 	}
507 
508 	/*
509 	 * Check the log page scope setting. If the log is said to be namespace
510 	 * scoped, then we'll allow the namespace to be specified. If it
511 	 * supports a different scope as well, then we'll default to the
512 	 * controller scope and this field is optional. Otherwise, it'll be
513 	 * required and it will be a mandatory field.
514 	 */
515 	if ((disc->nld_scope & NVME_LOG_SCOPE_NS) != 0) {
516 		req->nlr_allow |= 1 << NVME_LOG_REQ_FIELD_NSID;
517 		if ((disc->nld_scope & ~NVME_LOG_SCOPE_NS) != 0) {
518 			req->nlr_flags |= NVME_LOG_REQ_F_BCAST_NS_OK;
519 			req->nlr_nsid = NVME_NSID_BCAST;
520 		} else {
521 			req->nlr_need |= 1 << NVME_LOG_REQ_FIELD_NSID;
522 		}
523 	}
524 
525 	*reqp = req;
526 	return (nvme_ctrl_success(ctrl));
527 }
528 
529 typedef struct {
530 	bool nlia_found;
531 	const char *nlia_name;
532 	nvme_log_req_t *nlia_req;
533 	nvme_log_disc_t **nlia_discp;
534 	nvme_err_data_t nlia_err;
535 } nvme_log_init_arg_t;
536 
537 static bool
nvme_log_req_init_by_name_cb(nvme_ctrl_t * ctrl,const nvme_log_disc_t * disc,void * arg)538 nvme_log_req_init_by_name_cb(nvme_ctrl_t *ctrl, const nvme_log_disc_t *disc,
539     void *arg)
540 {
541 	nvme_log_init_arg_t *init = arg;
542 
543 	if (strcmp(init->nlia_name, disc->nld_short) != 0) {
544 		return (true);
545 	}
546 
547 	init->nlia_found = true;
548 	if (!nvme_log_req_init_by_disc(ctrl, disc, &init->nlia_req)) {
549 		nvme_ctrl_err_save(ctrl, &init->nlia_err);
550 		init->nlia_req = NULL;
551 	} else if (init->nlia_discp != NULL) {
552 		if (!nvme_log_disc_dup(ctrl, disc, init->nlia_discp)) {
553 			nvme_ctrl_err_save(ctrl, &init->nlia_err);
554 			nvme_log_req_fini(init->nlia_req);
555 			init->nlia_req = NULL;
556 		}
557 	}
558 
559 	return (false);
560 }
561 
562 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)563 nvme_log_req_init_by_name(nvme_ctrl_t *ctrl, const char *name, uint32_t flags,
564     nvme_log_disc_t **discp, nvme_log_req_t **reqp)
565 {
566 	nvme_log_init_arg_t init;
567 
568 	/*
569 	 * We consider discp an optional argument and therefore do not check it
570 	 * unlike name and reqp.
571 	 */
572 	if (reqp == NULL) {
573 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
574 		    "encountered invalid nvme_log_req_t output pointer: %p",
575 		    reqp));
576 	}
577 
578 	if (name == NULL) {
579 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
580 		    "encountered invalid pointer for log page name: %p", name));
581 	}
582 
583 	(void) memset(&init, 0, sizeof (init));
584 	init.nlia_name = name;
585 	init.nlia_discp = discp;
586 
587 	if (!nvme_log_discover(ctrl, NVME_LOG_SCOPE_CTRL |
588 	    NVME_LOG_SCOPE_NVM | NVME_LOG_SCOPE_NS, flags,
589 	    nvme_log_req_init_by_name_cb, &init)) {
590 		return (false);
591 	}
592 
593 	if (!init.nlia_found) {
594 		return (nvme_ctrl_error(ctrl, NVME_ERR_LOG_NAME_UNKNOWN, 0,
595 		    "failed to find log page with name %s", name));
596 	}
597 
598 	/*
599 	 * If we failed to create the request, but we did find it, then that
600 	 * means something went wrong and we can go ahead and already return an
601 	 * error.
602 	 */
603 	if (init.nlia_req == NULL) {
604 		nvme_ctrl_err_set(ctrl, &init.nlia_err);
605 		return (false);
606 	}
607 
608 	*reqp = init.nlia_req;
609 	return (nvme_ctrl_success(ctrl));
610 }
611 
612 static void
nvme_log_req_set_need(nvme_log_req_t * req,nvme_log_req_field_t field)613 nvme_log_req_set_need(nvme_log_req_t *req, nvme_log_req_field_t field)
614 {
615 	req->nlr_need |= 1 << field;
616 }
617 
618 static void
nvme_log_req_clear_need(nvme_log_req_t * req,nvme_log_req_field_t field)619 nvme_log_req_clear_need(nvme_log_req_t *req, nvme_log_req_field_t field)
620 {
621 	req->nlr_need &= ~(1 << field);
622 }
623 
624 static const nvme_field_check_t nvme_log_check_lid = {
625 	nvme_log_fields, NVME_LOG_REQ_FIELD_LID,
626 	NVME_ERR_LOG_LID_RANGE, 0, 0
627 };
628 
629 bool
nvme_log_req_set_lid(nvme_log_req_t * req,uint32_t lid)630 nvme_log_req_set_lid(nvme_log_req_t *req, uint32_t lid)
631 {
632 	if (!nvme_field_check_one(req->nlr_ctrl, lid, "get log page",
633 	    &nvme_log_check_lid, req->nlr_allow)) {
634 		return (false);
635 	}
636 
637 	req->nlr_lid = lid;
638 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_LID);
639 	return (nvme_ctrl_success(req->nlr_ctrl));
640 }
641 
642 static const nvme_field_check_t nvme_log_check_lsp = {
643 	nvme_log_fields, NVME_LOG_REQ_FIELD_LSP,
644 	NVME_ERR_LOG_LSP_RANGE, NVME_ERR_LOG_LSP_UNSUP,
645 	NVME_ERR_LOG_LSP_UNUSE
646 };
647 
648 bool
nvme_log_req_set_lsp(nvme_log_req_t * req,uint32_t lsp)649 nvme_log_req_set_lsp(nvme_log_req_t *req, uint32_t lsp)
650 {
651 	if (!nvme_field_check_one(req->nlr_ctrl, lsp, "get log page",
652 	    &nvme_log_check_lsp, req->nlr_allow)) {
653 		return (false);
654 	}
655 
656 	req->nlr_lsp = lsp;
657 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_LSP);
658 	return (nvme_ctrl_success(req->nlr_ctrl));
659 }
660 
661 static const nvme_field_check_t nvme_log_check_lsi = {
662 	nvme_log_fields, NVME_LOG_REQ_FIELD_LSI,
663 	NVME_ERR_LOG_LSI_RANGE, NVME_ERR_LOG_LSI_UNSUP,
664 	NVME_ERR_LOG_LSI_UNUSE
665 };
666 
667 bool
nvme_log_req_set_lsi(nvme_log_req_t * req,uint32_t lsi)668 nvme_log_req_set_lsi(nvme_log_req_t *req, uint32_t lsi)
669 {
670 	if (!nvme_field_check_one(req->nlr_ctrl, lsi, "get log page",
671 	    &nvme_log_check_lsi, req->nlr_allow)) {
672 		return (false);
673 	}
674 
675 	req->nlr_lsi = lsi;
676 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_LSI);
677 	return (nvme_ctrl_success(req->nlr_ctrl));
678 }
679 
680 static const nvme_field_check_t nvme_log_check_csi = {
681 	nvme_log_fields, NVME_LOG_REQ_FIELD_CSI,
682 	NVME_ERR_LOG_CSI_RANGE, NVME_ERR_LOG_CSI_UNSUP, 0
683 };
684 
685 bool
nvme_log_req_set_csi(nvme_log_req_t * req,nvme_csi_t csi)686 nvme_log_req_set_csi(nvme_log_req_t *req, nvme_csi_t csi)
687 {
688 	if (!nvme_field_check_one(req->nlr_ctrl, csi, "get log page",
689 	    &nvme_log_check_csi, req->nlr_allow)) {
690 		return (false);
691 	}
692 
693 	req->nlr_csi = csi;
694 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_CSI);
695 	return (nvme_ctrl_success(req->nlr_ctrl));
696 }
697 
698 static const nvme_field_check_t nvme_log_check_size = {
699 	nvme_log_fields, NVME_LOG_REQ_FIELD_SIZE,
700 	NVME_ERR_LOG_SIZE_RANGE, 0, 0
701 };
702 
703 bool
nvme_log_req_set_output(nvme_log_req_t * req,void * buf,size_t buflen)704 nvme_log_req_set_output(nvme_log_req_t *req, void *buf, size_t buflen)
705 {
706 	if (buf == NULL) {
707 		return (nvme_ctrl_error(req->nlr_ctrl, NVME_ERR_BAD_PTR, 0,
708 		    "log request output buffer cannot be NULL"));
709 	}
710 
711 	if (!nvme_field_check_one(req->nlr_ctrl, buflen, "get log page",
712 	    &nvme_log_check_size, req->nlr_allow)) {
713 		return (false);
714 	}
715 
716 	req->nlr_output = buf;
717 	req->nlr_output_len = buflen;
718 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_SIZE);
719 	return (nvme_ctrl_success(req->nlr_ctrl));
720 }
721 
722 bool
nvme_log_req_clear_output(nvme_log_req_t * req)723 nvme_log_req_clear_output(nvme_log_req_t *req)
724 {
725 	req->nlr_output = NULL;
726 	req->nlr_output_len = 0;
727 
728 	/*
729 	 * We can always set that we need this again as every log page requires
730 	 * a size being set. See the default allow settings for the field in
731 	 * nvme_log_fields[] and nvme_log_req_init_by_disc().
732 	 */
733 	nvme_log_req_set_need(req, NVME_LOG_REQ_FIELD_SIZE);
734 	return (nvme_ctrl_success(req->nlr_ctrl));
735 }
736 
737 static const nvme_field_check_t nvme_log_check_offset = {
738 	nvme_log_fields, NVME_LOG_REQ_FIELD_OFFSET,
739 	NVME_ERR_LOG_OFFSET_RANGE, 0, 0
740 };
741 
742 bool
nvme_log_req_set_offset(nvme_log_req_t * req,uint64_t off)743 nvme_log_req_set_offset(nvme_log_req_t *req, uint64_t off)
744 {
745 	if (!nvme_field_check_one(req->nlr_ctrl, off, "get log page",
746 	    &nvme_log_check_offset, req->nlr_allow)) {
747 		return (false);
748 	}
749 
750 	req->nlr_offset = off;
751 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_OFFSET);
752 	return (nvme_ctrl_success(req->nlr_ctrl));
753 }
754 
755 static const nvme_field_check_t nvme_log_check_nsid = {
756 	nvme_log_fields, NVME_LOG_REQ_FIELD_NSID, NVME_ERR_NS_RANGE, 0, 0
757 };
758 
759 bool
nvme_log_req_set_nsid(nvme_log_req_t * req,uint32_t nsid)760 nvme_log_req_set_nsid(nvme_log_req_t *req, uint32_t nsid)
761 {
762 	nvme_ctrl_t *ctrl = req->nlr_ctrl;
763 
764 	if (nsid == NVME_NSID_BCAST &&
765 	    (req->nlr_flags & NVME_LOG_REQ_F_BCAST_NS_OK) == 0) {
766 		return (nvme_ctrl_error(ctrl, NVME_ERR_NS_RANGE, 0, "the all "
767 		    "namespaces/controller nsid (0x%x) is not allowed for this "
768 		    "log page, valid namespaces are [0x%x, 0x%x]", nsid,
769 		    NVME_NSID_MIN, req->nlr_ctrl->nc_info.id_nn));
770 	}
771 
772 	if (!nvme_field_check_one(req->nlr_ctrl, nsid, "get log page",
773 	    &nvme_log_check_nsid, req->nlr_allow)) {
774 		return (false);
775 	}
776 
777 	req->nlr_nsid = nsid;
778 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_NSID);
779 	return (nvme_ctrl_success(req->nlr_ctrl));
780 }
781 
782 static const nvme_field_check_t nvme_log_check_rae = {
783 	nvme_log_fields, NVME_LOG_REQ_FIELD_RAE,
784 	NVME_ERR_LOG_RAE_RANGE, NVME_ERR_LOG_RAE_UNSUP,
785 	NVME_ERR_LOG_RAE_UNUSE
786 };
787 
788 bool
nvme_log_req_set_rae(nvme_log_req_t * req,bool rae)789 nvme_log_req_set_rae(nvme_log_req_t *req, bool rae)
790 {
791 	if (!nvme_field_check_one(req->nlr_ctrl, rae, "get log page",
792 	    &nvme_log_check_rae, req->nlr_allow)) {
793 		return (false);
794 	}
795 
796 	if (rae) {
797 		req->nlr_flags |= NVME_LOG_REQ_F_RAE;
798 	} else {
799 		req->nlr_flags &= ~NVME_LOG_REQ_F_RAE;
800 	}
801 	nvme_log_req_clear_need(req, NVME_LOG_REQ_FIELD_RAE);
802 	return (nvme_ctrl_success(req->nlr_ctrl));
803 }
804 
805 bool
nvme_log_req_exec(nvme_log_req_t * req)806 nvme_log_req_exec(nvme_log_req_t *req)
807 {
808 	nvme_ctrl_t *ctrl = req->nlr_ctrl;
809 	nvme_ioctl_get_logpage_t log;
810 
811 	if (req->nlr_need != 0) {
812 		return (nvme_field_miss_err(ctrl, nvme_log_fields,
813 		    nvme_log_nfields, NVME_ERR_LOG_REQ_MISSING_FIELDS,
814 		    "get log page", req->nlr_need));
815 	}
816 
817 	(void) memset(&log, 0, sizeof (nvme_ioctl_get_logpage_t));
818 	log.nigl_common.nioc_nsid = req->nlr_nsid;
819 	log.nigl_csi = req->nlr_csi;
820 	log.nigl_lid = req->nlr_lid;
821 	log.nigl_lsp = req->nlr_lsp;
822 	log.nigl_lsi = req->nlr_lsi;
823 	if ((req->nlr_flags & NVME_LOG_REQ_F_RAE) != 0) {
824 		log.nigl_rae = 1;
825 	}
826 	log.nigl_len = req->nlr_output_len;
827 	log.nigl_offset = req->nlr_offset;
828 	log.nigl_data = (uintptr_t)req->nlr_output;
829 
830 	if (ioctl(ctrl->nc_fd, NVME_IOC_GET_LOGPAGE, &log) != 0) {
831 		int e = errno;
832 		return (nvme_ioctl_syserror(ctrl, e, "get log page"));
833 	}
834 
835 	if (log.nigl_common.nioc_drv_err != NVME_IOCTL_E_OK) {
836 		return (nvme_ioctl_error(ctrl, &log.nigl_common,
837 		    "get log page"));
838 	}
839 
840 	return (nvme_ctrl_success(ctrl));
841 }
842