firmware.c revision 252672
1/*-
2 * Copyright (c) 2013 EMC Corp.
3 * All rights reserved.
4 *
5 * Copyright (C) 2012-2013 Intel Corporation
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD: head/sbin/nvmecontrol/firmware.c 252672 2013-07-04 00:26:24Z jimharris $");
32
33#include <sys/param.h>
34#include <sys/ioccom.h>
35#include <sys/stat.h>
36#include <sys/types.h>
37
38#include <ctype.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <stdbool.h>
42#include <stddef.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <sysexits.h>
47#include <unistd.h>
48
49#include "nvmecontrol.h"
50
51static int
52slot_has_valid_firmware(int fd, int slot)
53{
54	struct nvme_firmware_page	fw;
55	int				has_fw = false;
56
57	read_logpage(fd, NVME_LOG_FIRMWARE_SLOT,
58	    NVME_GLOBAL_NAMESPACE_TAG, &fw, sizeof(fw));
59
60	if (fw.revision[slot-1] != 0LLU)
61		has_fw = true;
62
63	return (has_fw);
64}
65
66static void
67read_image_file(char *path, void **buf, ssize_t *size)
68{
69	struct stat	sb;
70	int		fd;
71
72	*size = 0;
73	*buf = NULL;
74
75	if ((fd = open(path, O_RDONLY)) < 0) {
76		fprintf(stderr, "Unable to open '%s'.\n", path);
77		exit(EX_IOERR);
78	}
79	if (fstat(fd, &sb) < 0) {
80		fprintf(stderr, "Unable to stat '%s'.\n", path);
81		close(fd);
82		exit(EX_IOERR);
83	}
84	if ((*buf = malloc(sb.st_size)) == NULL) {
85		fprintf(stderr, "Unable to malloc %jd bytes.\n",
86		    sb.st_size);
87		close(fd);
88		exit(EX_IOERR);
89	}
90	if ((*size = read(fd, *buf, sb.st_size)) < 0) {
91		fprintf(stderr, "Error reading '%s', errno=%d (%s)\n",
92		    path, errno, strerror(errno));
93		close(fd);
94		exit(EX_IOERR);
95	}
96	if (*size != sb.st_size) {
97		fprintf(stderr, "Error reading '%s', "
98		    "read %zd bytes, requested %jd bytes\n",
99		    path, *size, sb.st_size);
100		close(fd);
101		exit(EX_IOERR);
102	}
103}
104
105static void
106update_firmware(int fd, uint8_t *payload, uint32_t payload_size)
107{
108	struct nvme_pt_command	pt;
109	size_t			size;
110	void			*chunk;
111	uint32_t		off, resid;
112	int			exit_code = EX_OK;
113
114	off = 0;
115	resid = payload_size;
116
117	if ((chunk = malloc((size_t)NVME_MAX_XFER_SIZE)) == NULL) {
118		printf("Unable to malloc %d bytes.\n", NVME_MAX_XFER_SIZE);
119		exit(EX_IOERR);
120	}
121
122	while (resid > 0) {
123		size = (resid >= NVME_MAX_XFER_SIZE) ?
124		    NVME_MAX_XFER_SIZE : resid;
125		memcpy(chunk, payload + off, size);
126
127		memset(&pt, 0, sizeof(pt));
128		pt.cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD;
129		pt.cmd.cdw10 = (size / sizeof(uint32_t)) - 1;
130		pt.cmd.cdw11 = (off / sizeof(uint32_t));
131		pt.buf = chunk;
132		pt.len = size;
133		pt.is_read = 0;
134
135		if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) {
136			printf("Firmware image download request failed. "
137			    "errno=%d (%s)\n",
138			    errno, strerror(errno));
139			exit_code = EX_IOERR;
140			break;
141		}
142
143		if (nvme_completion_is_error(&pt.cpl)) {
144			printf("Passthrough command returned error.\n");
145			exit_code = EX_IOERR;
146			break;
147		}
148
149		resid -= size;
150		off += size;
151	}
152
153	if (exit_code != EX_OK)
154		exit(exit_code);
155}
156
157static void
158activate_firmware(int fd, int slot, int activate_action)
159{
160	struct nvme_pt_command	pt;
161
162	memset(&pt, 0, sizeof(pt));
163	pt.cmd.opc = NVME_OPC_FIRMWARE_ACTIVATE;
164	pt.cmd.cdw10 = (activate_action << 3) | slot;
165	pt.is_read = 0;
166
167	if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) {
168		printf("Firmware activate request failed. errno=%d (%s)\n",
169		    errno, strerror(errno));
170		exit(EX_IOERR);
171	}
172
173	if (nvme_completion_is_error(&pt.cpl)) {
174		printf("Passthrough command returned error.\n");
175		exit(EX_IOERR);
176	}
177}
178
179static void
180firmware_usage(void)
181{
182	fprintf(stderr, "usage:\n");
183	fprintf(stderr, FIRMWARE_USAGE);
184	exit(EX_USAGE);
185}
186
187void
188firmware(int argc, char *argv[])
189{
190	int				fd = -1, slot = 0;
191	int				a_flag, s_flag, f_flag;
192	char				ch, *p, *image = NULL;
193	char				*controller = NULL, prompt[64];
194	void				*buf = NULL;
195	ssize_t				size;
196	struct nvme_controller_data	cdata;
197
198	a_flag = s_flag = f_flag = false;
199
200	while ((ch = getopt(argc, argv, "af:s:")) != -1) {
201		switch (ch) {
202		case 'a':
203			a_flag = true;
204			break;
205		case 's':
206			slot = strtol(optarg, &p, 0);
207			if (p != NULL && *p != '\0') {
208				fprintf(stderr,
209				    "\"%s\" not valid slot.\n",
210				    optarg);
211				firmware_usage();
212			} else if (slot == 0) {
213				fprintf(stderr,
214				    "0 is not a valid slot number. "
215				    "Slot numbers start at 1.\n");
216				firmware_usage();
217			} else if (slot > 7) {
218				fprintf(stderr,
219				    "Slot number %s specified which is "
220				    "greater than max allowed slot number of "
221				    "7.\n", optarg);
222				firmware_usage();
223			}
224			s_flag = true;
225			break;
226		case 'f':
227			image = optarg;
228			f_flag = true;
229			break;
230		}
231	}
232
233	/* Check that a controller (and not a namespace) was specified. */
234	if (optind >= argc || strstr(argv[optind], NVME_NS_PREFIX) != NULL)
235		firmware_usage();
236
237	if (!f_flag && !a_flag) {
238		fprintf(stderr,
239		    "Neither a replace ([-f path_to_firmware]) nor "
240		    "activate ([-a]) firmware image action\n"
241		    "was specified.\n");
242		firmware_usage();
243	}
244
245	if (!f_flag && a_flag && slot == 0) {
246		fprintf(stderr,
247		    "Slot number to activate not specified.\n");
248		firmware_usage();
249	}
250
251	controller = argv[optind];
252	open_dev(controller, &fd, 1, 1);
253	read_controller_data(fd, &cdata);
254
255	if (cdata.oacs.firmware == 0) {
256		fprintf(stderr,
257		    "Controller does not support firmware "
258		    "activate/download.\n");
259		exit(EX_IOERR);
260	}
261
262	if (f_flag && slot == 1 && cdata.frmw.slot1_ro) {
263		fprintf(stderr, "Slot %d is marked as read only.\n", slot);
264		exit(EX_IOERR);
265	}
266
267	if (slot > cdata.frmw.num_slots) {
268		fprintf(stderr,
269		    "Slot %d was specified but controller only "
270		    "supports %d firmware slots.\n",
271		    slot, cdata.frmw.num_slots);
272		exit(EX_IOERR);
273	}
274
275	if (!slot_has_valid_firmware(fd, slot)) {
276		fprintf(stderr,
277		    "Slot %d does not contain valid firmware.\n"
278		    "Try 'nvmecontrol logpage -p 3 %s' to get a list "
279		    "of available firmware images.\n",
280		    slot, controller);
281		exit(EX_IOERR);
282	}
283
284	if (f_flag && a_flag)
285		printf("You are about to download and activate "
286		       "firmware image (%s) to controller %s.\n"
287		       "This may damage your controller and/or "
288		       "overwrite an existing firmware image.\n",
289		       image, controller);
290	else if (a_flag)
291		printf("You are about to activate a new firmware "
292		       "image on controller %s.\n"
293		       "This may damage your controller.\n",
294		       controller);
295	else if (f_flag)
296		printf("You are about to download firmware image "
297		       "(%s) to controller %s.\n"
298		       "This may damage your controller and/or "
299		       "overwrite an existing firmware image.\n",
300		       image, controller);
301
302	printf("Are you sure you want to continue? (yes/no) ");
303	while (1) {
304		fgets(prompt, sizeof(prompt), stdin);
305		if (strncasecmp(prompt, "yes", 3) == 0)
306			break;
307		if (strncasecmp(prompt, "no", 2) == 0)
308			exit(EX_OK);
309		printf("Please answer \"yes\" or \"no\". ");
310	}
311
312	if (f_flag) {
313		read_image_file(image, &buf, &size);
314		update_firmware(fd, buf, size);
315		if (a_flag)
316			activate_firmware(fd, slot,
317			    NVME_AA_REPLACE_ACTIVATE);
318		else
319			activate_firmware(fd, slot,
320			    NVME_AA_REPLACE_NO_ACTIVATE);
321	} else {
322		activate_firmware(fd, slot, NVME_AA_ACTIVATE);
323	}
324
325	if (a_flag) {
326		printf("New firmware image activated and will take "
327		       "effect after next controller reset.\n"
328		       "Controller reset can be initiated via "
329		       "'nvmecontrol reset %s'\n",
330		       controller);
331	}
332
333	close(fd);
334	exit(EX_OK);
335}
336