xref: /illumos-gate/usr/src/lib/libnvme/common/libnvme_fw.c (revision e00bdde3c6d406f40f53f3025defadc22f7ec31a)
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