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
nvme_fw_load(nvme_ctrl_t * ctrl,const void * buf,size_t len,uint64_t off)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
nvme_fw_commit_req_fini(nvme_fw_commit_req_t * req)83 nvme_fw_commit_req_fini(nvme_fw_commit_req_t *req)
84 {
85 free(req);
86 }
87
88 bool
nvme_fw_commit_req_init(nvme_ctrl_t * ctrl,nvme_fw_commit_req_t ** reqp)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
nvme_fw_commit_req_clear_need(nvme_fw_commit_req_t * req,nvme_fw_commit_req_field_t field)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
nvme_fw_commit_req_set_slot(nvme_fw_commit_req_t * req,uint32_t slot)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
nvme_fw_commit_req_set_action(nvme_fw_commit_req_t * req,uint32_t act)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
nvme_fw_commit_req_exec(nvme_fw_commit_req_t * req)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