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 31 nvme_id_req_fini(nvme_id_req_t *idreq) 32 { 33 free(idreq); 34 } 35 36 bool 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 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 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 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 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 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 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 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