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 iterators for the various NVMe identify related features that
18 * return lists of information (rather than the basic data structures). These
19 * are all phrased as iterators to the user so that way we can abstract around
20 * the fact that there may be additional commands required to make this happen
21 * or eventually a number of namespaces that exceeds the basic amount supported
22 * here.
23 */
24
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "libnvme_impl.h"
29
30 void
nvme_id_req_fini(nvme_id_req_t * idreq)31 nvme_id_req_fini(nvme_id_req_t *idreq)
32 {
33 free(idreq);
34 }
35
36 bool
nvme_id_req_init_by_cns(nvme_ctrl_t * ctrl,nvme_csi_t csi,uint32_t cns,nvme_id_req_t ** idreqp)37 nvme_id_req_init_by_cns(nvme_ctrl_t *ctrl, nvme_csi_t csi, uint32_t cns,
38 nvme_id_req_t **idreqp)
39 {
40 const nvme_identify_info_t *info = NULL;
41 nvme_id_req_t *req;
42 nvme_valid_ctrl_data_t ctrl_data;
43
44 if (idreqp == NULL) {
45 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
46 "encountered invalid nvme_id_req_t output pointer: %p",
47 idreqp));
48 }
49
50 for (size_t i = 0; i < nvme_identify_ncmds; i++) {
51 if (nvme_identify_cmds[i].nii_csi == csi &&
52 nvme_identify_cmds[i].nii_cns == cns) {
53 info = &nvme_identify_cmds[i];
54 break;
55 }
56 }
57
58 if (info == NULL) {
59 return (nvme_ctrl_error(ctrl, NVME_ERR_IDENTIFY_UNKNOWN, 0,
60 "unknown identify command CSI/CNS 0x%x/0x%x", csi, cns));
61 }
62
63 ctrl_data.vcd_vers = &ctrl->nc_vers;
64 ctrl_data.vcd_id = &ctrl->nc_info;
65
66 if (!nvme_identify_info_supported(info, &ctrl_data)) {
67 return (nvme_ctrl_error(ctrl, NVME_ERR_IDENTIFY_UNSUP_BY_DEV, 0,
68 "device does not support identify command %s (CSI/CNS "
69 "0x%x/0x%x)", info->nii_name, info->nii_csi,
70 info->nii_cns));
71 }
72
73 req = calloc(1, sizeof (nvme_id_req_t));
74 if (req == NULL) {
75 int e = errno;
76 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e,
77 "failed to allocate memory for a new nvme_id_req_t: %s",
78 strerror(e)));
79 }
80
81 req->nir_ctrl = ctrl;
82 req->nir_info = info;
83
84 /*
85 * The identify command always wants to write a 4 KiB buffer
86 * (NVME_IDENTIFY_BUFSIZE) out and therefore we manually tack this onto
87 * to the need and allow list.
88 */
89 req->nir_need = info->nii_fields | (1 << NVME_ID_REQ_F_BUF);
90 req->nir_allow = info->nii_fields | (1 << NVME_ID_REQ_F_BUF);
91
92 *idreqp = req;
93 return (nvme_ctrl_success(ctrl));
94 }
95
96 static void
nvme_id_req_set_need(nvme_id_req_t * req,nvme_identify_req_field_t field)97 nvme_id_req_set_need(nvme_id_req_t *req, nvme_identify_req_field_t field)
98 {
99 req->nir_need |= 1 << field;
100 }
101
102 static void
nvme_id_req_clear_need(nvme_id_req_t * req,nvme_identify_req_field_t field)103 nvme_id_req_clear_need(nvme_id_req_t *req, nvme_identify_req_field_t field)
104 {
105 req->nir_need &= ~(1 << field);
106 }
107
108 static const nvme_field_check_t nvme_identify_check_nsid = {
109 nvme_identify_fields, NVME_ID_REQ_F_NSID,
110 NVME_ERR_NS_RANGE, 0, NVME_ERR_NS_UNUSE
111 };
112
113 bool
nvme_id_req_set_nsid(nvme_id_req_t * req,uint32_t nsid)114 nvme_id_req_set_nsid(nvme_id_req_t *req, uint32_t nsid)
115 {
116 nvme_ctrl_t *ctrl = req->nir_ctrl;
117 nvme_identify_info_flags_t flags = req->nir_info->nii_flags;
118
119 /*
120 * In some contexts the NSID here must refer to an actual valid
121 * namespace. In other cases it's referring to a search index and
122 * therefore all we care about is the value. Finally, sometimes the
123 * broadcast address is used to access things that are common across all
124 * namespaces. If we have a list operation, we just pass this through to
125 * the kernel. This unfortunately requires a bit more manual checking.
126 */
127 if ((flags & NVME_IDENTIFY_INFO_F_NSID_LIST) == 0 &&
128 !nvme_field_check_one(req->nir_ctrl, nsid, "identify",
129 &nvme_identify_check_nsid, req->nir_allow)) {
130 return (false);
131 }
132
133 if ((flags & NVME_IDENTIFY_INFO_F_NSID_LIST) == 0 &&
134 (req->nir_allow & (1 << NVME_ID_REQ_F_NSID)) != 0) {
135 if (nsid == 0) {
136 return (nvme_ctrl_error(ctrl, NVME_ERR_NS_RANGE, 0,
137 "namespaces id 0x%x is invalid, valid namespaces "
138 "are [0x%x, 0x%x]", nsid, NVME_NSID_MIN,
139 req->nir_ctrl->nc_info.id_nn));
140 }
141
142 if (nsid == NVME_NSID_BCAST &&
143 (flags & NVME_IDENTIFY_INFO_F_BCAST) == 0) {
144 return (nvme_ctrl_error(ctrl, NVME_ERR_NS_RANGE, 0,
145 "the all namespaces/controller nsid (0x%x) is not "
146 "allowed for this identify command, valid "
147 "namespaces are [0x%x, 0x%x]", nsid,
148 NVME_NSID_MIN, req->nir_ctrl->nc_info.id_nn));
149
150 }
151 }
152
153 req->nir_nsid = nsid;
154 nvme_id_req_clear_need(req, NVME_ID_REQ_F_NSID);
155 return (nvme_ctrl_success(req->nir_ctrl));
156 }
157
158 static const nvme_field_check_t nvme_identify_check_ctrlid = {
159 nvme_identify_fields, NVME_ID_REQ_F_CTRLID,
160 NVME_ERR_IDENTIFY_CTRLID_RANGE, NVME_ERR_IDENTIFY_CTRLID_UNSUP,
161 NVME_ERR_IDENTIFY_CTRLID_UNUSE
162 };
163
164 bool
nvme_id_req_set_ctrlid(nvme_id_req_t * req,uint32_t ctrlid)165 nvme_id_req_set_ctrlid(nvme_id_req_t *req, uint32_t ctrlid)
166 {
167 if (!nvme_field_check_one(req->nir_ctrl, ctrlid, "identify",
168 &nvme_identify_check_ctrlid, req->nir_allow)) {
169 return (false);
170 }
171
172 req->nir_ctrlid = ctrlid;
173 nvme_id_req_clear_need(req, NVME_ID_REQ_F_CTRLID);
174 return (nvme_ctrl_success(req->nir_ctrl));
175 }
176
177 bool
nvme_id_req_set_output(nvme_id_req_t * req,void * buf,size_t len)178 nvme_id_req_set_output(nvme_id_req_t *req, void *buf, size_t len)
179 {
180 nvme_ctrl_t *ctrl = req->nir_ctrl;
181
182 if (buf == NULL) {
183 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
184 "identify output buffer cannot be NULL"));
185 }
186
187 if (len < NVME_IDENTIFY_BUFSIZE) {
188 return (nvme_ctrl_error(ctrl, NVME_ERR_IDENTIFY_OUTPUT_RANGE, 0,
189 "identify buffer size must be at least %u bytes large",
190 NVME_IDENTIFY_BUFSIZE));
191 }
192
193 req->nir_buf = buf;
194 nvme_id_req_clear_need(req, NVME_ID_REQ_F_BUF);
195 return (nvme_ctrl_success(req->nir_ctrl));
196 }
197
198 bool
nvme_id_req_clear_output(nvme_id_req_t * req)199 nvme_id_req_clear_output(nvme_id_req_t *req)
200 {
201 req->nir_buf = NULL;
202
203 /*
204 * This field is always required so we can just toss a blanket set need
205 * on here.
206 */
207 nvme_id_req_set_need(req, NVME_ID_REQ_F_BUF);
208 return (nvme_ctrl_success(req->nir_ctrl));
209 }
210
211 bool
nvme_id_req_exec(nvme_id_req_t * req)212 nvme_id_req_exec(nvme_id_req_t *req)
213 {
214 nvme_ctrl_t *ctrl = req->nir_ctrl;
215 nvme_ioctl_identify_t id;
216
217 if (req->nir_need != 0) {
218 return (nvme_field_miss_err(ctrl, nvme_identify_fields,
219 nvme_identify_nfields, NVME_ERR_IDENTIFY_REQ_MISSING_FIELDS,
220 "identify", req->nir_need));
221 }
222
223 (void) memset(&id, 0, sizeof (nvme_ioctl_identify_t));
224 id.nid_common.nioc_nsid = req->nir_nsid;
225 id.nid_cns = req->nir_info->nii_cns;
226 id.nid_ctrlid = req->nir_ctrlid;
227 id.nid_data = (uintptr_t)req->nir_buf;
228
229 if (ioctl(req->nir_ctrl->nc_fd, NVME_IOC_IDENTIFY, &id) != 0) {
230 int e = errno;
231 return (nvme_ioctl_syserror(ctrl, e, "identify"));
232 }
233
234 if (id.nid_common.nioc_drv_err != NVME_IOCTL_E_OK) {
235 return (nvme_ioctl_error(ctrl, &id.nid_common, "identify"));
236 }
237
238 return (nvme_ctrl_success(ctrl));
239 }
240