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 file implements the firmware download and commit pieces of libnvme. 18 */ 19 20 #include <string.h> 21 #include <unistd.h> 22 23 #include "libnvme_impl.h" 24 25 static const nvme_field_check_t nvme_fw_load_check_numd = { 26 nvme_fw_load_fields, NVME_FW_LOAD_REQ_FIELD_NUMD, 27 NVME_ERR_FW_LOAD_LEN_RANGE, 0, 0 28 }; 29 30 static const nvme_field_check_t nvme_fw_load_check_offset = { 31 nvme_fw_load_fields, NVME_FW_LOAD_REQ_FIELD_OFFSET, 32 NVME_ERR_FW_LOAD_OFFSET_RANGE, 0, 0 33 }; 34 35 bool 36 nvme_fw_load(nvme_ctrl_t *ctrl, const void *buf, size_t len, uint64_t off) 37 { 38 nvme_ioctl_fw_load_t fw; 39 nvme_valid_ctrl_data_t data; 40 41 if (buf == NULL) { 42 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0, 43 "encountered invalid data buffer pointer: %p", buf)); 44 } 45 46 data.vcd_vers = &ctrl->nc_vers; 47 data.vcd_id = &ctrl->nc_info; 48 49 if (!nvme_fw_cmds_supported(&data)) { 50 return (nvme_ctrl_error(ctrl, NVME_ERR_FW_UNSUP_BY_DEV, 0, 51 "controller does not support firmware download")); 52 } 53 54 if (!nvme_field_check_one(ctrl, len, "firmware download", 55 &nvme_fw_load_check_numd, 0)) { 56 return (false); 57 } 58 59 if (!nvme_field_check_one(ctrl, off, "firmware download", 60 &nvme_fw_load_check_offset, 0)) { 61 return (false); 62 } 63 64 (void) memset(&fw, 0, sizeof (fw)); 65 fw.fwl_buf = (uintptr_t)buf; 66 fw.fwl_len = len; 67 fw.fwl_off = off; 68 69 if (ioctl(ctrl->nc_fd, NVME_IOC_FIRMWARE_DOWNLOAD, &fw) != 0) { 70 int e = errno; 71 return (nvme_ioctl_syserror(ctrl, e, "firmware load")); 72 } 73 74 if (fw.fwl_common.nioc_drv_err != NVME_IOCTL_E_OK) { 75 return (nvme_ioctl_error(ctrl, &fw.fwl_common, 76 "firmware load")); 77 } 78 79 return (nvme_ctrl_success(ctrl)); 80 } 81 82 void 83 nvme_fw_commit_req_fini(nvme_fw_commit_req_t *req) 84 { 85 free(req); 86 } 87 88 bool 89 nvme_fw_commit_req_init(nvme_ctrl_t *ctrl, nvme_fw_commit_req_t **reqp) 90 { 91 nvme_fw_commit_req_t *req; 92 nvme_valid_ctrl_data_t data; 93 94 if (reqp == NULL) { 95 return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0, 96 "encountered invalid nvme_commit_req_t output pointer: %p", 97 reqp)); 98 } 99 100 data.vcd_vers = &ctrl->nc_vers; 101 data.vcd_id = &ctrl->nc_info; 102 103 if (!nvme_fw_cmds_supported(&data)) { 104 return (nvme_ctrl_error(ctrl, NVME_ERR_FW_UNSUP_BY_DEV, 0, 105 "controller does not support firmware download")); 106 } 107 108 req = calloc(1, sizeof (nvme_fw_commit_req_t)); 109 if (req == NULL) { 110 int e = errno; 111 return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to " 112 "allocate memory for a new nvme_log_req_t: %s", 113 strerror(e))); 114 } 115 116 req->fwc_ctrl = ctrl; 117 118 for (size_t i = 0; i < nvme_fw_commit_nfields; i++) { 119 if (nvme_fw_commit_fields[i].nlfi_def_req) { 120 req->fwc_need |= 1 << i; 121 } 122 } 123 124 *reqp = req; 125 return (nvme_ctrl_success(ctrl)); 126 } 127 128 static void 129 nvme_fw_commit_req_clear_need(nvme_fw_commit_req_t *req, 130 nvme_fw_commit_req_field_t field) 131 { 132 req->fwc_need &= ~(1 << field); 133 } 134 135 static const nvme_field_check_t nvme_fw_commit_check_slot = { 136 nvme_fw_commit_fields, NVME_FW_COMMIT_REQ_FIELD_SLOT, 137 NVME_ERR_FW_COMMIT_SLOT_RANGE, 0, 0 138 }; 139 140 bool 141 nvme_fw_commit_req_set_slot(nvme_fw_commit_req_t *req, uint32_t slot) 142 { 143 if (!nvme_field_check_one(req->fwc_ctrl, slot, "firmware commit", 144 &nvme_fw_commit_check_slot, 0)) { 145 return (false); 146 } 147 148 req->fwc_slot = slot; 149 nvme_fw_commit_req_clear_need(req, NVME_FW_COMMIT_REQ_FIELD_SLOT); 150 return (nvme_ctrl_success(req->fwc_ctrl)); 151 } 152 153 static const nvme_field_check_t nvme_fw_commit_check_act = { 154 nvme_fw_commit_fields, NVME_FW_COMMIT_REQ_FIELD_ACT, 155 NVME_ERR_FW_COMMIT_ACTION_RANGE, 0, 0 156 }; 157 158 bool 159 nvme_fw_commit_req_set_action(nvme_fw_commit_req_t *req, uint32_t act) 160 { 161 if (!nvme_field_check_one(req->fwc_ctrl, act, "firmware commit", 162 &nvme_fw_commit_check_act, 0)) { 163 return (false); 164 } 165 166 req->fwc_action = act; 167 nvme_fw_commit_req_clear_need(req, NVME_FW_COMMIT_REQ_FIELD_ACT); 168 return (nvme_ctrl_success(req->fwc_ctrl)); 169 } 170 171 bool 172 nvme_fw_commit_req_exec(nvme_fw_commit_req_t *req) 173 { 174 nvme_ctrl_t *ctrl = req->fwc_ctrl; 175 nvme_ioctl_fw_commit_t fw; 176 177 if (req->fwc_need != 0) { 178 return (nvme_field_miss_err(ctrl, nvme_fw_commit_fields, 179 nvme_fw_commit_nfields, 180 NVME_ERR_FW_COMMIT_REQ_MISSING_FIELDS, "firmware commit", 181 req->fwc_need)); 182 } 183 184 (void) memset(&fw, 0, sizeof (fw)); 185 fw.fwc_slot = req->fwc_slot; 186 fw.fwc_action = req->fwc_action; 187 188 if (ioctl(ctrl->nc_fd, NVME_IOC_FIRMWARE_COMMIT, &fw) != 0) { 189 int e = errno; 190 return (nvme_ioctl_syserror(ctrl, e, "firmware commit")); 191 } 192 193 if (fw.fwc_common.nioc_drv_err != NVME_IOCTL_E_OK) { 194 return (nvme_ioctl_error(ctrl, &fw.fwc_common, 195 "firmware commit")); 196 } 197 198 return (nvme_ctrl_success(ctrl)); 199 } 200