xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm_ofmt.c (revision ce64215f896462c714523539f9bcd839fafa7fb0)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2025 Oxide Computer Company
14  * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
15  */
16 
17 /*
18  * nvmeadm output formatting for ofmt based rendering
19  */
20 
21 #include <strings.h>
22 #include <sys/sysmacros.h>
23 #include <err.h>
24 
25 #include "nvmeadm.h"
26 
27 typedef struct {
28 	uint32_t	nb_flag;
29 	const char	*nb_str;
30 } nvmeadm_bitstr_t;
31 
32 static boolean_t
nvmeadm_bits_to_str(uint32_t val,const nvmeadm_bitstr_t * strs,size_t nstrs,char * buf,size_t buflen)33 nvmeadm_bits_to_str(uint32_t val, const nvmeadm_bitstr_t *strs, size_t nstrs,
34     char *buf, size_t buflen)
35 {
36 	boolean_t comma = B_FALSE;
37 
38 	buf[0] = '\0';
39 	for (size_t i = 0; i < nstrs; i++) {
40 		if ((val & strs[i].nb_flag) != strs[i].nb_flag)
41 			continue;
42 		if (comma && strlcat(buf, ",", buflen) >= buflen)
43 			return (B_FALSE);
44 		if (strlcat(buf, strs[i].nb_str, buflen) >= buflen)
45 			return (B_FALSE);
46 		comma = true;
47 	}
48 
49 	if (buf[0] == '\0') {
50 		if (strlcat(buf, "--", buflen) >= buflen)
51 			return (B_FALSE);
52 	}
53 
54 	return (B_TRUE);
55 }
56 
57 typedef enum nvme_list_ofmt_field {
58 	NVME_LIST_MODEL,
59 	NVME_LIST_SERIAL,
60 	NVME_LIST_FWREV,
61 	NVME_LIST_VERSION,
62 	NVME_LIST_SIZE,
63 	NVME_LIST_CAPACITY,
64 	NVME_LIST_USED,
65 	NVME_LIST_INSTANCE,
66 	NVME_LIST_NAMESPACE,
67 	NVME_LIST_DISK,
68 	NVME_LIST_UNALLOC,
69 	NVME_LIST_NS_STATE,
70 	NVME_LIST_CTRLPATH
71 } nvme_list_ofmt_field_t;
72 
73 static boolean_t
nvmeadm_list_common_ofmt_cb(ofmt_arg_t * ofmt_arg,char * buf,uint_t buflen)74 nvmeadm_list_common_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
75 {
76 	nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
77 	nvme_ctrl_info_t *ctrl = list->nloa_ctrl;
78 	const nvme_version_t *vers;
79 	char *path;
80 	size_t ret;
81 
82 	switch (ofmt_arg->ofmt_id) {
83 	case NVME_LIST_MODEL:
84 		ret = strlcpy(buf, nvme_ctrl_info_model(ctrl), buflen);
85 		break;
86 	case NVME_LIST_SERIAL:
87 		ret = strlcpy(buf, nvme_ctrl_info_serial(ctrl), buflen);
88 		break;
89 	case NVME_LIST_FWREV:
90 		ret = strlcpy(buf, nvme_ctrl_info_fwrev(ctrl), buflen);
91 		break;
92 	case NVME_LIST_VERSION:
93 		vers = nvme_ctrl_info_version(ctrl);
94 		ret = snprintf(buf, buflen, "%u.%u", vers->v_major,
95 		    vers->v_minor);
96 		break;
97 	case NVME_LIST_INSTANCE:
98 		ret = strlcpy(buf, list->nloa_name, buflen);
99 		break;
100 	case NVME_LIST_CTRLPATH:
101 		if (list->nloa_dip == DI_NODE_NIL) {
102 			return (B_FALSE);
103 		}
104 
105 		path = di_devfs_path(list->nloa_dip);
106 		if (path == NULL) {
107 			return (B_FALSE);
108 		}
109 		ret = strlcat(buf, path, buflen);
110 		di_devfs_path_free(path);
111 		break;
112 	default:
113 		warnx("internal programmer error: encountered unknown ofmt "
114 		    "argument id 0x%x", ofmt_arg->ofmt_id);
115 		abort();
116 	}
117 	if (ret >= buflen) {
118 		return (B_FALSE);
119 	}
120 	return (B_TRUE);
121 }
122 
123 static boolean_t
nvmeadm_list_ctrl_ofmt_cb(ofmt_arg_t * ofmt_arg,char * buf,uint_t buflen)124 nvmeadm_list_ctrl_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
125 {
126 	nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
127 	nvme_ctrl_info_t *ctrl = list->nloa_ctrl;
128 	nvme_uint128_t u128;
129 	size_t ret;
130 
131 	switch (ofmt_arg->ofmt_id) {
132 	case NVME_LIST_CAPACITY:
133 		if (nvme_ctrl_info_cap(ctrl, &u128)) {
134 			ret = nvme_snprint_uint128(buf, buflen, u128, 0, 0);
135 		} else {
136 			return (B_FALSE);
137 		}
138 		break;
139 	case NVME_LIST_UNALLOC:
140 		if (nvme_ctrl_info_unalloc_cap(ctrl, &u128)) {
141 			ret = nvme_snprint_uint128(buf, buflen, u128, 0, 0);
142 		} else {
143 			return (B_FALSE);
144 		}
145 		break;
146 	default:
147 		warnx("internal programmer error: encountered unknown ofmt "
148 		    "argument id 0x%x", ofmt_arg->ofmt_id);
149 		abort();
150 	}
151 
152 	if (ret >= buflen) {
153 		return (B_FALSE);
154 	}
155 	return (B_TRUE);
156 }
157 
158 static boolean_t
nvmeadm_list_nsid_ofmt_cb(ofmt_arg_t * ofmt_arg,char * buf,uint_t buflen)159 nvmeadm_list_nsid_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
160 {
161 	nvmeadm_list_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
162 	nvme_ns_info_t *ns = list->nloa_ns;
163 	const nvme_nvm_lba_fmt_t *fmt = NULL;
164 	uint64_t val;
165 	size_t ret;
166 
167 
168 	(void) nvme_ns_info_curformat(ns, &fmt);
169 
170 	switch (ofmt_arg->ofmt_id) {
171 	case NVME_LIST_NAMESPACE:
172 		ret = snprintf(buf, buflen, "%u", nvme_ns_info_nsid(ns));
173 		break;
174 	case NVME_LIST_DISK:
175 		if (list->nloa_disk != NULL) {
176 			ret = strlcpy(buf, list->nloa_disk, buflen);
177 		} else {
178 			return (B_FALSE);
179 		}
180 		break;
181 	case NVME_LIST_SIZE:
182 		if (nvme_ns_info_size(ns, &val) && fmt != NULL) {
183 			val *= nvme_nvm_lba_fmt_data_size(fmt);
184 			ret = snprintf(buf, buflen, "%" PRIu64, val);
185 		} else {
186 			return (B_FALSE);
187 		}
188 		break;
189 	case NVME_LIST_CAPACITY:
190 		if (nvme_ns_info_size(ns, &val) && fmt != NULL) {
191 			val *= nvme_nvm_lba_fmt_data_size(fmt);
192 			ret = snprintf(buf, buflen, "%" PRIu64, val);
193 		} else {
194 			return (B_FALSE);
195 		}
196 		break;
197 	case NVME_LIST_USED:
198 		if (nvme_ns_info_size(ns, &val) && fmt != NULL) {
199 			val *= nvme_nvm_lba_fmt_data_size(fmt);
200 			ret = snprintf(buf, buflen, "%" PRIu64, val);
201 		} else {
202 			return (B_FALSE);
203 		}
204 		break;
205 	case NVME_LIST_NS_STATE:
206 		ret = strlcpy(buf, list->nloa_state, buflen);
207 		break;
208 	default:
209 		warnx("internal programmer error: encountered unknown ofmt "
210 		    "argument id 0x%x", ofmt_arg->ofmt_id);
211 		abort();
212 	}
213 
214 	if (ret >= buflen) {
215 		return (B_FALSE);
216 	}
217 	return (B_TRUE);
218 }
219 
220 const ofmt_field_t nvmeadm_list_ctrl_ofmt[] = {
221 	{ "MODEL", 30, NVME_LIST_MODEL, nvmeadm_list_common_ofmt_cb },
222 	{ "SERIAL", 30, NVME_LIST_SERIAL, nvmeadm_list_common_ofmt_cb },
223 	{ "FWREV", 10, NVME_LIST_FWREV, nvmeadm_list_common_ofmt_cb },
224 	{ "VERSION", 10, NVME_LIST_VERSION, nvmeadm_list_common_ofmt_cb },
225 	{ "CAPACITY", 15, NVME_LIST_CAPACITY, nvmeadm_list_ctrl_ofmt_cb },
226 	{ "INSTANCE", 10, NVME_LIST_INSTANCE, nvmeadm_list_common_ofmt_cb },
227 	{ "UNALLOCATED", 15, NVME_LIST_UNALLOC, nvmeadm_list_ctrl_ofmt_cb },
228 	{ "CTRLPATH", 30, NVME_LIST_CTRLPATH, nvmeadm_list_common_ofmt_cb },
229 	{ NULL, 0, 0, NULL }
230 };
231 
232 const ofmt_field_t nvmeadm_list_nsid_ofmt[] = {
233 	{ "MODEL", 30, NVME_LIST_MODEL, nvmeadm_list_common_ofmt_cb },
234 	{ "SERIAL", 30, NVME_LIST_SERIAL, nvmeadm_list_common_ofmt_cb },
235 	{ "FWREV", 10, NVME_LIST_FWREV, nvmeadm_list_common_ofmt_cb },
236 	{ "VERSION", 10, NVME_LIST_VERSION, nvmeadm_list_common_ofmt_cb },
237 	{ "SIZE", 15, NVME_LIST_SIZE, nvmeadm_list_nsid_ofmt_cb },
238 	{ "CAPACITY", 15, NVME_LIST_CAPACITY, nvmeadm_list_nsid_ofmt_cb },
239 	{ "USED", 15, NVME_LIST_USED, nvmeadm_list_nsid_ofmt_cb },
240 	{ "INSTANCE", 10, NVME_LIST_INSTANCE, nvmeadm_list_common_ofmt_cb },
241 	{ "NAMESPACE", 10, NVME_LIST_NAMESPACE, nvmeadm_list_nsid_ofmt_cb },
242 	{ "DISK", 15, NVME_LIST_DISK, nvmeadm_list_nsid_ofmt_cb },
243 	{ "NS-STATE", 10, NVME_LIST_NS_STATE, nvmeadm_list_nsid_ofmt_cb },
244 	{ "CTRLPATH", 30, NVME_LIST_CTRLPATH, nvmeadm_list_common_ofmt_cb },
245 	{ NULL, 0, 0, NULL }
246 };
247 
248 typedef enum {
249 	NVME_LIST_LOGS_DEVICE,
250 	NVME_LIST_LOGS_NAME,
251 	NVME_LIST_LOGS_DESC,
252 	NVME_LIST_LOGS_SCOPE,
253 	NVME_LIST_LOGS_FIELDS,
254 	NVME_LIST_LOGS_CSI,
255 	NVME_LIST_LOGS_LID,
256 	NVME_LIST_LOGS_SIZE,
257 	NVME_LIST_LOGS_MINSIZE,
258 	NVME_LIST_LOGS_IMPL,
259 	NVME_LIST_LOGS_SOURCES,
260 	NVME_LIST_LOGS_KIND
261 } nvme_list_logs_ofmt_field_t;
262 
263 static const nvmeadm_bitstr_t nvmeadm_log_scopes[] = {
264 	{ NVME_LOG_SCOPE_CTRL, "controller" },
265 	{ NVME_LOG_SCOPE_NVM, "nvm" },
266 	{ NVME_LOG_SCOPE_NS, "namespace" }
267 };
268 
269 static const nvmeadm_bitstr_t nvmeadm_log_fields[] = {
270 	{ NVME_LOG_DISC_F_NEED_LSP, "lsp" },
271 	{ NVME_LOG_DISC_F_NEED_LSI, "lsi" },
272 	{ NVME_LOG_DISC_F_NEED_RAE, "rae" }
273 };
274 
275 static const nvmeadm_bitstr_t nvmeadm_log_sources[] = {
276 	{ NVME_LOG_DISC_S_SPEC, "spec" },
277 	{ NVME_LOG_DISC_S_ID_CTRL, "identify-controller" },
278 	{ NVME_LOG_DISC_S_DB, "internal-db" },
279 	{ NVME_LOG_DISC_S_CMD, "command" }
280 };
281 
282 static boolean_t
nvmeadm_list_logs_ofmt_cb(ofmt_arg_t * ofmt_arg,char * buf,uint_t buflen)283 nvmeadm_list_logs_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
284 {
285 	const nvmeadm_list_logs_ofmt_arg_t *list = ofmt_arg->ofmt_cbarg;
286 	const nvme_log_disc_t *disc = list->nlloa_disc;
287 	uint64_t alloc;
288 	size_t ret;
289 	nvme_log_size_kind_t kind;
290 
291 	switch (ofmt_arg->ofmt_id) {
292 	case NVME_LIST_LOGS_DEVICE:
293 		ret = strlcpy(buf, list->nlloa_name, buflen);
294 		break;
295 	case NVME_LIST_LOGS_NAME:
296 		ret = strlcpy(buf, nvme_log_disc_name(disc), buflen);
297 		break;
298 	case NVME_LIST_LOGS_DESC:
299 		ret = strlcpy(buf, nvme_log_disc_desc(disc), buflen);
300 		break;
301 	case NVME_LIST_LOGS_SCOPE:
302 		return (nvmeadm_bits_to_str(nvme_log_disc_scopes(disc),
303 		    nvmeadm_log_scopes, ARRAY_SIZE(nvmeadm_log_scopes), buf,
304 		    buflen));
305 	case NVME_LIST_LOGS_FIELDS:
306 		return (nvmeadm_bits_to_str(nvme_log_disc_fields(disc),
307 		    nvmeadm_log_fields, ARRAY_SIZE(nvmeadm_log_fields), buf,
308 		    buflen));
309 		break;
310 	case NVME_LIST_LOGS_CSI:
311 		switch (nvme_log_disc_csi(disc)) {
312 		case NVME_CSI_NVM:
313 			ret = strlcpy(buf, "nvm", buflen);
314 			break;
315 		case NVME_CSI_KV:
316 			ret = strlcpy(buf, "kv", buflen);
317 			break;
318 		case NVME_CSI_ZNS:
319 			ret = strlcpy(buf, "zns", buflen);
320 			break;
321 		default:
322 			ret = snprintf(buf, buflen, "unknown (0x%x)",
323 			    nvme_log_disc_csi(disc));
324 			break;
325 		}
326 		break;
327 	case NVME_LIST_LOGS_LID:
328 		ret = snprintf(buf, buflen, "0x%x", nvme_log_disc_lid(disc));
329 		break;
330 	case NVME_LIST_LOGS_SIZE:
331 	case NVME_LIST_LOGS_MINSIZE:
332 		kind = nvme_log_disc_size(disc, &alloc);
333 
334 		if (kind == NVME_LOG_SIZE_K_UNKNOWN) {
335 			return (B_FALSE);
336 		}
337 
338 		if (kind == NVME_LOG_SIZE_K_VAR &&
339 		    ofmt_arg->ofmt_id == NVME_LIST_LOGS_SIZE) {
340 			return (B_FALSE);
341 		}
342 
343 		ret = snprintf(buf, buflen, "%" PRIu64, alloc);
344 		break;
345 	case NVME_LIST_LOGS_IMPL:
346 		ret = strlcpy(buf, nvme_log_disc_impl(disc) ? "yes" : "no",
347 		    buflen);
348 		break;
349 	case NVME_LIST_LOGS_SOURCES:
350 		return (nvmeadm_bits_to_str(nvme_log_disc_sources(disc),
351 		    nvmeadm_log_sources, ARRAY_SIZE(nvmeadm_log_sources), buf,
352 		    buflen));
353 		break;
354 	case NVME_LIST_LOGS_KIND:
355 		switch (nvme_log_disc_kind(disc)) {
356 		case NVME_LOG_ID_MANDATORY:
357 			ret = strlcpy(buf, "mandatory", buflen);
358 			break;
359 		case NVME_LOG_ID_OPTIONAL:
360 			ret = strlcpy(buf, "optional", buflen);
361 			break;
362 		case NVME_LOG_ID_VENDOR_SPECIFIC:
363 			ret = strlcpy(buf, "vendor-specific", buflen);
364 			break;
365 		default:
366 			ret = snprintf(buf, buflen, "unknown (0x%x)",
367 			    nvme_log_disc_kind(disc));
368 			break;
369 		}
370 		break;
371 	default:
372 		warnx("internal programmer error: encountered unknown ofmt "
373 		    "argument id 0x%x", ofmt_arg->ofmt_id);
374 		abort();
375 	}
376 
377 	return (ret < buflen);
378 }
379 
380 const char *nvmeadm_list_logs_fields = "device,name,scope,fields,desc";
381 const char *nvmeadm_list_logs_fields_impl = "device,name,scope,impl,fields,"
382 	"desc";
383 const ofmt_field_t nvmeadm_list_logs_ofmt[] = {
384 	{ "DEVICE", 8, NVME_LIST_LOGS_DEVICE, nvmeadm_list_logs_ofmt_cb },
385 	{ "NAME", 18, NVME_LIST_LOGS_NAME, nvmeadm_list_logs_ofmt_cb },
386 	{ "DESC", 30, NVME_LIST_LOGS_DESC, nvmeadm_list_logs_ofmt_cb },
387 	{ "SCOPE", 14, NVME_LIST_LOGS_SCOPE, nvmeadm_list_logs_ofmt_cb },
388 	{ "FIELDS", 10, NVME_LIST_LOGS_FIELDS, nvmeadm_list_logs_ofmt_cb },
389 	{ "CSI", 6, NVME_LIST_LOGS_CSI, nvmeadm_list_logs_ofmt_cb },
390 	{ "LID", 6, NVME_LIST_LOGS_LID, nvmeadm_list_logs_ofmt_cb },
391 	{ "SIZE", 10, NVME_LIST_LOGS_SIZE, nvmeadm_list_logs_ofmt_cb },
392 	{ "MINSIZE", 10, NVME_LIST_LOGS_MINSIZE, nvmeadm_list_logs_ofmt_cb },
393 	{ "IMPL", 6, NVME_LIST_LOGS_IMPL, nvmeadm_list_logs_ofmt_cb },
394 	{ "SOURCES", 20, NVME_LIST_LOGS_SOURCES, nvmeadm_list_logs_ofmt_cb },
395 	{ "KIND", 16, NVME_LIST_LOGS_KIND, nvmeadm_list_logs_ofmt_cb },
396 	{ NULL, 0, 0, NULL }
397 };
398 
399 typedef enum {
400 	NVME_LIST_FEATS_DEVICE,
401 	NVME_LIST_FEATS_SHORT,
402 	NVME_LIST_FEATS_SPEC,
403 	NVME_LIST_FEATS_FID,
404 	NVME_LIST_FEATS_SCOPE,
405 	NVME_LIST_FEATS_KIND,
406 	NVME_LIST_FEATS_CSI,
407 	NVME_LIST_FEATS_FLAGS,
408 	NVME_LIST_FEATS_GET_IN,
409 	NVME_LIST_FEATS_SET_IN,
410 	NVME_LIST_FEATS_GET_OUT,
411 	NVME_LIST_FEATS_SET_OUT,
412 	NVME_LIST_FEATS_DATA_LEN,
413 	NVME_LIST_FEATS_IMPL
414 } nvme_list_features_ofmt_field_t;
415 
416 static const nvmeadm_bitstr_t nvmeadm_feat_scopes[] = {
417 	{ NVME_FEAT_SCOPE_CTRL, "controller" },
418 	{ NVME_FEAT_SCOPE_NS, "namespace" }
419 };
420 
421 static const nvmeadm_bitstr_t nvmeadm_feat_get_in[] = {
422 	{ NVME_GET_FEAT_F_CDW11, "cdw11" },
423 	{ NVME_GET_FEAT_F_DATA, "data" },
424 	{ NVME_GET_FEAT_F_NSID, "nsid" }
425 };
426 
427 static const nvmeadm_bitstr_t nvmeadm_feat_set_in[] = {
428 	{ NVME_SET_FEAT_F_CDW11, "cdw11" },
429 	{ NVME_SET_FEAT_F_CDW12, "cdw12" },
430 	{ NVME_SET_FEAT_F_CDW13, "cdw13" },
431 	{ NVME_SET_FEAT_F_CDW14, "cdw14" },
432 	{ NVME_SET_FEAT_F_CDW15, "cdw15" },
433 	{ NVME_SET_FEAT_F_DATA, "data" },
434 	{ NVME_SET_FEAT_F_NSID, "nsid" }
435 };
436 
437 static const nvmeadm_bitstr_t nvmeadm_feat_output[] = {
438 	{ NVME_FEAT_OUTPUT_CDW0, "cdw0" },
439 	{ NVME_FEAT_OUTPUT_DATA, "data" }
440 };
441 
442 static const nvmeadm_bitstr_t nvmeadm_feat_flags[] = {
443 	{ NVME_FEAT_F_GET_BCAST_NSID, "get-bcastns" },
444 	{ NVME_FEAT_F_SET_BCAST_NSID, "set-bcastns" }
445 };
446 
447 static const nvmeadm_bitstr_t nvmeadm_feat_csi[] = {
448 	{ NVME_FEAT_CSI_NVM, "nvm" }
449 };
450 
451 static boolean_t
nvmeadm_list_features_ofmt_cb(ofmt_arg_t * ofmt_arg,char * buf,uint_t buflen)452 nvmeadm_list_features_ofmt_cb(ofmt_arg_t *ofmt_arg, char *buf, uint_t buflen)
453 {
454 	const nvmeadm_list_features_ofmt_arg_t *nlfo = ofmt_arg->ofmt_cbarg;
455 	const nvme_feat_disc_t *feat = nlfo->nlfoa_feat;
456 	size_t ret;
457 
458 	switch (ofmt_arg->ofmt_id) {
459 	case NVME_LIST_FEATS_DEVICE:
460 		ret = strlcpy(buf, nlfo->nlfoa_name, buflen);
461 		break;
462 	case NVME_LIST_FEATS_SHORT:
463 		ret = strlcpy(buf, nvme_feat_disc_short(feat), buflen);
464 		break;
465 	case NVME_LIST_FEATS_SPEC:
466 		ret = strlcpy(buf, nvme_feat_disc_spec(feat), buflen);
467 		break;
468 	case NVME_LIST_FEATS_FID:
469 		ret = snprintf(buf, buflen, "0x%x", nvme_feat_disc_fid(feat));
470 		break;
471 	case NVME_LIST_FEATS_SCOPE:
472 		return (nvmeadm_bits_to_str(nvme_feat_disc_scope(feat),
473 		    nvmeadm_feat_scopes, ARRAY_SIZE(nvmeadm_feat_scopes), buf,
474 		    buflen));
475 	case NVME_LIST_FEATS_KIND:
476 		switch (nvme_feat_disc_kind(feat)) {
477 		case NVME_FEAT_MANDATORY:
478 			ret = strlcpy(buf, "mandatory", buflen);
479 			break;
480 		case NVME_FEAT_OPTIONAL:
481 			ret = strlcpy(buf, "optional", buflen);
482 			break;
483 		case NVME_FEAT_VENDOR_SPECIFIC:
484 			ret = strlcpy(buf, "vendor-specific", buflen);
485 			break;
486 		default:
487 			ret = snprintf(buf, buflen, "unknown (0x%x)",
488 			    nvme_feat_disc_kind(feat));
489 			break;
490 		}
491 		break;
492 	case NVME_LIST_FEATS_CSI:
493 		if (nvme_feat_disc_csi(feat) == NVME_FEAT_CSI_NONE) {
494 			ret = strlcpy(buf, "none", buflen);
495 			break;
496 		}
497 
498 		return (nvmeadm_bits_to_str(nvme_feat_disc_csi(feat),
499 		    nvmeadm_feat_csi, ARRAY_SIZE(nvmeadm_feat_csi), buf,
500 		    buflen));
501 	case NVME_LIST_FEATS_FLAGS:
502 		return (nvmeadm_bits_to_str(nvme_feat_disc_flags(feat),
503 		    nvmeadm_feat_flags, ARRAY_SIZE(nvmeadm_feat_flags), buf,
504 		    buflen));
505 	case NVME_LIST_FEATS_GET_IN:
506 		return (nvmeadm_bits_to_str(nvme_feat_disc_fields_get(feat),
507 		    nvmeadm_feat_get_in, ARRAY_SIZE(nvmeadm_feat_get_in), buf,
508 		    buflen));
509 	case NVME_LIST_FEATS_SET_IN:
510 		return (nvmeadm_bits_to_str(nvme_feat_disc_fields_set(feat),
511 		    nvmeadm_feat_set_in, ARRAY_SIZE(nvmeadm_feat_set_in), buf,
512 		    buflen));
513 	case NVME_LIST_FEATS_GET_OUT:
514 		return (nvmeadm_bits_to_str(nvme_feat_disc_output_get(feat),
515 		    nvmeadm_feat_output, ARRAY_SIZE(nvmeadm_feat_output), buf,
516 		    buflen));
517 	case NVME_LIST_FEATS_SET_OUT:
518 		return (nvmeadm_bits_to_str(nvme_feat_disc_output_set(feat),
519 		    nvmeadm_feat_output, ARRAY_SIZE(nvmeadm_feat_output), buf,
520 		    buflen));
521 	case NVME_LIST_FEATS_DATA_LEN:
522 		if (nvme_feat_disc_data_size(feat) == 0) {
523 			ret = strlcpy(buf, "-", buflen);
524 		} else {
525 			ret = snprintf(buf, buflen, "%" PRIu64,
526 			    nvme_feat_disc_data_size(feat));
527 		}
528 		break;
529 	case NVME_LIST_FEATS_IMPL:
530 		switch (nvme_feat_disc_impl(feat)) {
531 		case NVME_FEAT_IMPL_UNKNOWN:
532 			ret = strlcpy(buf, "unknown", buflen);
533 			break;
534 		case NVME_FEAT_IMPL_UNSUPPORTED:
535 			ret = strlcpy(buf, "no", buflen);
536 			break;
537 		case NVME_FEAT_IMPL_SUPPORTED:
538 			ret = strlcpy(buf, "yes", buflen);
539 			break;
540 		default:
541 			ret = snprintf(buf, buflen, "unknown (0x%x)",
542 			    nvme_feat_disc_impl(feat));
543 			break;
544 		}
545 		break;
546 	default:
547 		warnx("internal programmer error: encountered unknown ofmt "
548 		    "argument id 0x%x", ofmt_arg->ofmt_id);
549 		abort();
550 	}
551 
552 	return (ret < buflen);
553 }
554 
555 const char *nvmeadm_list_features_fields = "device,short,scope,impl,spec";
556 const ofmt_field_t nvmeadm_list_features_ofmt[] = {
557 	{ "DEVICE", 8, NVME_LIST_FEATS_DEVICE, nvmeadm_list_features_ofmt_cb },
558 	{ "SHORT", 14, NVME_LIST_FEATS_SHORT, nvmeadm_list_features_ofmt_cb },
559 	{ "SPEC", 30, NVME_LIST_FEATS_SPEC, nvmeadm_list_features_ofmt_cb },
560 	{ "FID", 6, NVME_LIST_FEATS_FID, nvmeadm_list_features_ofmt_cb },
561 	{ "SCOPE", 14, NVME_LIST_FEATS_SCOPE, nvmeadm_list_features_ofmt_cb },
562 	{ "KIND", 16, NVME_LIST_FEATS_KIND, nvmeadm_list_features_ofmt_cb },
563 	{ "CSI", 6, NVME_LIST_FEATS_CSI, nvmeadm_list_features_ofmt_cb },
564 	{ "FLAGS", 14, NVME_LIST_FEATS_FLAGS, nvmeadm_list_features_ofmt_cb },
565 	{ "GET-IN", 14, NVME_LIST_FEATS_GET_IN, nvmeadm_list_features_ofmt_cb },
566 	{ "SET-IN", 14, NVME_LIST_FEATS_SET_IN, nvmeadm_list_features_ofmt_cb },
567 	{ "GET-OUT", 14, NVME_LIST_FEATS_GET_OUT,
568 	    nvmeadm_list_features_ofmt_cb },
569 	{ "SET-OUT", 14, NVME_LIST_FEATS_SET_OUT,
570 	    nvmeadm_list_features_ofmt_cb },
571 	{ "DATALEN", 8, NVME_LIST_FEATS_DATA_LEN,
572 	    nvmeadm_list_features_ofmt_cb },
573 	{ "IMPL", 8, NVME_LIST_FEATS_IMPL, nvmeadm_list_features_ofmt_cb },
574 	{ NULL, 0, 0, NULL }
575 };
576