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 */
15
16 /*
17 * This file contains shared pieces of the NVMe field validation logic and has
18 * shared pieces that are used between different parts.
19 */
20
21 #include "nvme_common.h"
22
23 #include <sys/sysmacros.h>
24 #ifdef _KERNEL
25 #include <sys/sunddi.h>
26 #include <sys/stdint.h>
27 #else
28 #include <stdio.h>
29 #include <inttypes.h>
30 #endif
31
32 bool
nvme_field_atleast(const nvme_valid_ctrl_data_t * data,const nvme_version_t * targ)33 nvme_field_atleast(const nvme_valid_ctrl_data_t *data,
34 const nvme_version_t *targ)
35 {
36 return (nvme_vers_atleast(data->vcd_vers, targ));
37 }
38
39 /*
40 * Note, we rely on external logic to determine if the broadcast nsid is valid.
41 * We always accept it.
42 */
43 bool
nvme_field_valid_nsid(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,uint64_t nsid,char * msg,size_t msglen)44 nvme_field_valid_nsid(const nvme_field_info_t *field,
45 const nvme_valid_ctrl_data_t *data, uint64_t nsid, char *msg, size_t msglen)
46 {
47 if ((nsid != 0 && nsid <= data->vcd_id->id_nn) ||
48 nsid == NVME_NSID_BCAST) {
49 return (true);
50 }
51
52 (void) snprintf(msg, msglen, "namespace id %" PRIu64 "is outside the "
53 "valid range [0x%x, 0x%x], the broadcast nsid (0x%x) may be valid",
54 nsid, NVME_NSID_MIN, NVME_NSID_BCAST, data->vcd_id->id_nn);
55 return (false);
56 }
57
58 bool
nvme_field_range_check(const nvme_field_info_t * field,uint64_t min,uint64_t max,char * msg,size_t msglen,uint64_t value)59 nvme_field_range_check(const nvme_field_info_t *field, uint64_t min,
60 uint64_t max, char *msg, size_t msglen, uint64_t value)
61 {
62 if (value >= min && value <= max) {
63 return (true);
64 }
65
66 (void) snprintf(msg, msglen, "field %s (%s) value 0x%"
67 PRIx64 " is outside the valid range: [0x%" PRIx64 ", 0x%" PRIx64
68 "]", field->nlfi_human, field->nlfi_spec, value, min, max);
69 return (false);
70 }
71
72 bool
nvme_field_mask_check(const nvme_field_info_t * field,uint64_t valid_mask,char * msg,size_t msglen,uint64_t value)73 nvme_field_mask_check(const nvme_field_info_t *field, uint64_t valid_mask,
74 char *msg, size_t msglen, uint64_t value)
75 {
76 uint64_t inval = ~valid_mask & value;
77 if (inval == 0) {
78 return (true);
79 }
80
81 (void) snprintf(msg, msglen, "field %s (%s) value 0x%" PRIx64
82 " uses bits outside of the mask 0x%" PRIx64 ": 0x%" PRIx64,
83 field->nlfi_human, field->nlfi_spec, value, valid_mask, inval);
84 return (false);
85 }
86
87 /*
88 * This is a general validation function for fields that are part of a command.
89 * It will check if the field is supported by the controller and if so, that its
90 * value is within the expected range. On error, an optional message will be
91 * written that explains the error. This is intended to be shared between
92 * userland and the kernel. The kernel should pass NULL/0 for msg/msglen because
93 * there is no message translation capability in the kernel.
94 */
95 nvme_field_error_t
nvme_field_validate(const nvme_field_info_t * field,const nvme_valid_ctrl_data_t * data,uint64_t value,char * msg,size_t msglen)96 nvme_field_validate(const nvme_field_info_t *field,
97 const nvme_valid_ctrl_data_t *data, uint64_t value, char *msg,
98 size_t msglen)
99 {
100 ASSERT3P(field->nlfi_vers, !=, NULL);
101
102 if (msglen > 0)
103 *msg = '\0';
104
105 if (!nvme_field_atleast(data, field->nlfi_vers)) {
106 (void) snprintf(msg, msglen, "field %s (%s) requires "
107 "version %u.%u, but device is at %u.%u", field->nlfi_human,
108 field->nlfi_spec, data->vcd_vers->v_major,
109 data->vcd_vers->v_minor, field->nlfi_vers->v_major,
110 field->nlfi_vers->v_minor);
111 return (NVME_FIELD_ERR_UNSUP_VERSION);
112 }
113
114 if (field->nlfi_sup != NULL && !field->nlfi_sup(field, data, msg,
115 msglen)) {
116 (void) snprintf(msg, msglen, "field %s (%s) is not "
117 "supported by the controller", field->nlfi_human,
118 field->nlfi_spec);
119 return (NVME_FIELD_ERR_UNSUP_FIELD);
120 }
121
122 if (field->nlfi_valid != NULL) {
123 if (!field->nlfi_valid(field, data, value, msg, msglen)) {
124 if (msglen > 0 && *msg == '\0') {
125 (void) snprintf(msg, msglen,
126 "field %s (%s) value 0x%" PRIx64
127 " is invalid",
128 field->nlfi_human,
129 field->nlfi_spec, value);
130 }
131 return (NVME_FIELD_ERR_BAD_VALUE);
132 }
133 } else if (!nvme_field_range_check(field, 0, field->nlfi_max_size, msg,
134 msglen, value)) {
135 return (NVME_FIELD_ERR_BAD_VALUE);
136 }
137
138 return (NVME_FIELD_ERR_OK);
139 }
140