149fac610SJim Harris /*- 24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause 31de7b4b8SPedro F. Giffuni * 449fac610SJim Harris * Copyright (c) 2013 EMC Corp. 549fac610SJim Harris * All rights reserved. 649fac610SJim Harris * 749fac610SJim Harris * Copyright (C) 2012-2013 Intel Corporation 849fac610SJim Harris * All rights reserved. 949fac610SJim Harris * 1049fac610SJim Harris * Redistribution and use in source and binary forms, with or without 1149fac610SJim Harris * modification, are permitted provided that the following conditions 1249fac610SJim Harris * are met: 1349fac610SJim Harris * 1. Redistributions of source code must retain the above copyright 1449fac610SJim Harris * notice, this list of conditions and the following disclaimer. 1549fac610SJim Harris * 2. Redistributions in binary form must reproduce the above copyright 1649fac610SJim Harris * notice, this list of conditions and the following disclaimer in the 1749fac610SJim Harris * documentation and/or other materials provided with the distribution. 1849fac610SJim Harris * 1949fac610SJim Harris * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 2049fac610SJim Harris * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2149fac610SJim Harris * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2249fac610SJim Harris * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2349fac610SJim Harris * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2449fac610SJim Harris * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2549fac610SJim Harris * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2649fac610SJim Harris * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2749fac610SJim Harris * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2849fac610SJim Harris * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2949fac610SJim Harris * SUCH DAMAGE. 3049fac610SJim Harris */ 3149fac610SJim Harris 3249fac610SJim Harris #include <sys/param.h> 3349fac610SJim Harris #include <sys/ioccom.h> 3449fac610SJim Harris #include <sys/stat.h> 3549fac610SJim Harris #include <sys/types.h> 3649fac610SJim Harris 3749fac610SJim Harris #include <ctype.h> 38821ef73cSJim Harris #include <err.h> 3949fac610SJim Harris #include <fcntl.h> 40821ef73cSJim Harris #include <inttypes.h> 4149fac610SJim Harris #include <stdbool.h> 4249fac610SJim Harris #include <stddef.h> 4349fac610SJim Harris #include <stdio.h> 4449fac610SJim Harris #include <stdlib.h> 4549fac610SJim Harris #include <string.h> 465dc463f9SAlexander Motin #include <sysexits.h> 4749fac610SJim Harris #include <unistd.h> 4849fac610SJim Harris 4949fac610SJim Harris #include "nvmecontrol.h" 5049fac610SJim Harris 51f634b4c1SWarner Losh /* Tables for command line parsing */ 52f634b4c1SWarner Losh 53f634b4c1SWarner Losh static cmd_fn_t firmware; 54f634b4c1SWarner Losh 55f634b4c1SWarner Losh #define NONE 0xffffffffu 56f634b4c1SWarner Losh static struct options { 57f634b4c1SWarner Losh bool activate; 58f634b4c1SWarner Losh uint32_t slot; 59f634b4c1SWarner Losh const char *fw_img; 60f634b4c1SWarner Losh const char *dev; 61f634b4c1SWarner Losh } opt = { 62f634b4c1SWarner Losh .activate = false, 63f634b4c1SWarner Losh .slot = NONE, 64f634b4c1SWarner Losh .fw_img = NULL, 65f634b4c1SWarner Losh .dev = NULL, 66f634b4c1SWarner Losh }; 67f634b4c1SWarner Losh 68f634b4c1SWarner Losh static const struct opts firmware_opts[] = { 69f634b4c1SWarner Losh #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } 70f634b4c1SWarner Losh OPT("activate", 'a', arg_none, opt, activate, 71f634b4c1SWarner Losh "Attempt to activate firmware"), 72f634b4c1SWarner Losh OPT("slot", 's', arg_uint32, opt, slot, 73f634b4c1SWarner Losh "Slot to activate and/or download firmware to"), 74f634b4c1SWarner Losh OPT("firmware", 'f', arg_path, opt, fw_img, 75f634b4c1SWarner Losh "Firmware image to download"), 76f634b4c1SWarner Losh { NULL, 0, arg_none, NULL, NULL } 77f634b4c1SWarner Losh }; 78f634b4c1SWarner Losh #undef OPT 79f634b4c1SWarner Losh 80f634b4c1SWarner Losh static const struct args firmware_args[] = { 815458a1c8SAlexander Motin { arg_string, &opt.dev, "controller-id|namespace-id" }, 82f634b4c1SWarner Losh { arg_none, NULL, NULL }, 83f634b4c1SWarner Losh }; 84f634b4c1SWarner Losh 85f634b4c1SWarner Losh static struct cmd firmware_cmd = { 86f634b4c1SWarner Losh .name = "firmware", 87f634b4c1SWarner Losh .fn = firmware, 88e843651bSAlexander Motin .descr = "Download firmware image to controller", 89f634b4c1SWarner Losh .ctx_size = sizeof(opt), 90f634b4c1SWarner Losh .opts = firmware_opts, 91f634b4c1SWarner Losh .args = firmware_args, 92f634b4c1SWarner Losh }; 93f634b4c1SWarner Losh 94f634b4c1SWarner Losh CMD_COMMAND(firmware_cmd); 95f634b4c1SWarner Losh 96f634b4c1SWarner Losh /* End of tables for command line parsing */ 97a13a291aSWarner Losh 9849fac610SJim Harris static int 9949fac610SJim Harris slot_has_valid_firmware(int fd, int slot) 10049fac610SJim Harris { 10149fac610SJim Harris struct nvme_firmware_page fw; 10249fac610SJim Harris int has_fw = false; 10349fac610SJim Harris 10449fac610SJim Harris read_logpage(fd, NVME_LOG_FIRMWARE_SLOT, 105*98ab7d0aSWarner Losh NVME_GLOBAL_NAMESPACE_TAG, 0, 0, 0, 0, 0, 0, 0, 106*98ab7d0aSWarner Losh &fw, sizeof(fw)); 10749fac610SJim Harris 1081b38f851SJohn Baldwin if (fw.revision[slot-1][0] != '\0') 10949fac610SJim Harris has_fw = true; 11049fac610SJim Harris 11149fac610SJim Harris return (has_fw); 11249fac610SJim Harris } 11349fac610SJim Harris 11449fac610SJim Harris static void 115f634b4c1SWarner Losh read_image_file(const char *path, void **buf, int32_t *size) 11649fac610SJim Harris { 11749fac610SJim Harris struct stat sb; 118821ef73cSJim Harris int32_t filesize; 11949fac610SJim Harris int fd; 12049fac610SJim Harris 12149fac610SJim Harris *size = 0; 12249fac610SJim Harris *buf = NULL; 12349fac610SJim Harris 124821ef73cSJim Harris if ((fd = open(path, O_RDONLY)) < 0) 1255dc463f9SAlexander Motin err(EX_NOINPUT, "unable to open '%s'", path); 126821ef73cSJim Harris if (fstat(fd, &sb) < 0) 1275dc463f9SAlexander Motin err(EX_NOINPUT, "unable to stat '%s'", path); 128821ef73cSJim Harris 129821ef73cSJim Harris /* 130821ef73cSJim Harris * The NVMe spec does not explicitly state a maximum firmware image 131821ef73cSJim Harris * size, although one can be inferred from the dword size limitation 132821ef73cSJim Harris * for the size and offset fields in the Firmware Image Download 133821ef73cSJim Harris * command. 134821ef73cSJim Harris * 135821ef73cSJim Harris * Technically, the max is UINT32_MAX * sizeof(uint32_t), since the 136821ef73cSJim Harris * size and offsets are specified in terms of dwords (not bytes), but 137821ef73cSJim Harris * realistically INT32_MAX is sufficient here and simplifies matters 138821ef73cSJim Harris * a bit. 139821ef73cSJim Harris */ 140821ef73cSJim Harris if (sb.st_size > INT32_MAX) 1415dc463f9SAlexander Motin errx(EX_USAGE, "size of file '%s' is too large (%jd bytes)", 142821ef73cSJim Harris path, (intmax_t)sb.st_size); 143821ef73cSJim Harris filesize = (int32_t)sb.st_size; 144821ef73cSJim Harris if ((*buf = malloc(filesize)) == NULL) 1455dc463f9SAlexander Motin errx(EX_OSERR, "unable to malloc %d bytes", filesize); 146821ef73cSJim Harris if ((*size = read(fd, *buf, filesize)) < 0) 1475dc463f9SAlexander Motin err(EX_IOERR, "error reading '%s'", path); 148821ef73cSJim Harris /* XXX assuming no short reads */ 149821ef73cSJim Harris if (*size != filesize) 1505dc463f9SAlexander Motin errx(EX_IOERR, 151821ef73cSJim Harris "error reading '%s' (read %d bytes, requested %d bytes)", 152821ef73cSJim Harris path, *size, filesize); 1536995fb5eSDavid Bright close(fd); 15449fac610SJim Harris } 15549fac610SJim Harris 15649fac610SJim Harris static void 15716969d14SDavid Bright update_firmware(int fd, uint8_t *payload, int32_t payload_size, uint8_t fwug) 15849fac610SJim Harris { 15949fac610SJim Harris struct nvme_pt_command pt; 16016969d14SDavid Bright uint64_t max_xfer_size; 16144c52406SAdrian Chadd int32_t off; 16244c52406SAdrian Chadd uint32_t resid, size; 16349fac610SJim Harris void *chunk; 16449fac610SJim Harris 16549fac610SJim Harris off = 0; 16649fac610SJim Harris resid = payload_size; 16749fac610SJim Harris 168329327e2SAlexander Motin if (ioctl(fd, NVME_GET_MAX_XFER_SIZE, &max_xfer_size) < 0) 1695dc463f9SAlexander Motin err(EX_IOERR, "query max transfer size failed"); 170329327e2SAlexander Motin if (fwug != 0 && fwug != 0xFF) 171329327e2SAlexander Motin max_xfer_size = MIN(max_xfer_size, (uint64_t)fwug << 12); 17216969d14SDavid Bright 17316969d14SDavid Bright if ((chunk = aligned_alloc(PAGE_SIZE, max_xfer_size)) == NULL) 1745dc463f9SAlexander Motin errx(EX_OSERR, "unable to malloc %zd bytes", (size_t)max_xfer_size); 17549fac610SJim Harris 17649fac610SJim Harris while (resid > 0) { 17744c52406SAdrian Chadd size = (resid >= max_xfer_size) ? max_xfer_size : resid; 17849fac610SJim Harris memcpy(chunk, payload + off, size); 17949fac610SJim Harris 18049fac610SJim Harris memset(&pt, 0, sizeof(pt)); 1819544e6dcSChuck Tuffli pt.cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD; 1820d787e9bSWojciech Macek pt.cmd.cdw10 = htole32((size / sizeof(uint32_t)) - 1); 1830d787e9bSWojciech Macek pt.cmd.cdw11 = htole32(off / sizeof(uint32_t)); 18449fac610SJim Harris pt.buf = chunk; 18549fac610SJim Harris pt.len = size; 18649fac610SJim Harris pt.is_read = 0; 18749fac610SJim Harris 188821ef73cSJim Harris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 1895dc463f9SAlexander Motin err(EX_IOERR, "firmware download request failed"); 19049fac610SJim Harris 191821ef73cSJim Harris if (nvme_completion_is_error(&pt.cpl)) 1925dc463f9SAlexander Motin errx(EX_IOERR, "firmware download request returned error"); 19349fac610SJim Harris 19449fac610SJim Harris resid -= size; 19549fac610SJim Harris off += size; 19649fac610SJim Harris } 1976995fb5eSDavid Bright free(chunk); 19849fac610SJim Harris } 19949fac610SJim Harris 2004a14f9daSJim Harris static int 20149fac610SJim Harris activate_firmware(int fd, int slot, int activate_action) 20249fac610SJim Harris { 20349fac610SJim Harris struct nvme_pt_command pt; 2040d787e9bSWojciech Macek uint16_t sct, sc; 20549fac610SJim Harris 20649fac610SJim Harris memset(&pt, 0, sizeof(pt)); 2079544e6dcSChuck Tuffli pt.cmd.opc = NVME_OPC_FIRMWARE_ACTIVATE; 2080d787e9bSWojciech Macek pt.cmd.cdw10 = htole32((activate_action << 3) | slot); 20949fac610SJim Harris pt.is_read = 0; 21049fac610SJim Harris 211821ef73cSJim Harris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 2125dc463f9SAlexander Motin err(EX_IOERR, "firmware activate request failed"); 21349fac610SJim Harris 2140d787e9bSWojciech Macek sct = NVME_STATUS_GET_SCT(pt.cpl.status); 2150d787e9bSWojciech Macek sc = NVME_STATUS_GET_SC(pt.cpl.status); 2160d787e9bSWojciech Macek 2170d787e9bSWojciech Macek if (sct == NVME_SCT_COMMAND_SPECIFIC && 2180d787e9bSWojciech Macek sc == NVME_SC_FIRMWARE_REQUIRES_RESET) 2194a14f9daSJim Harris return 1; 2204a14f9daSJim Harris 221821ef73cSJim Harris if (nvme_completion_is_error(&pt.cpl)) 2225dc463f9SAlexander Motin errx(EX_IOERR, "firmware activate request returned error"); 2234a14f9daSJim Harris 2244a14f9daSJim Harris return 0; 22549fac610SJim Harris } 22649fac610SJim Harris 22749fac610SJim Harris static void 228f634b4c1SWarner Losh firmware(const struct cmd *f, int argc, char *argv[]) 22949fac610SJim Harris { 230f634b4c1SWarner Losh int fd = -1; 2314a14f9daSJim Harris int activate_action, reboot_required; 232f634b4c1SWarner Losh char prompt[64]; 23349fac610SJim Harris void *buf = NULL; 2345458a1c8SAlexander Motin char *path; 235a7bf63beSAlexander Motin int32_t size = 0, nsid; 2360d787e9bSWojciech Macek uint16_t oacs_fw; 2370d787e9bSWojciech Macek uint8_t fw_slot1_ro, fw_num_slots; 23849fac610SJim Harris struct nvme_controller_data cdata; 23949fac610SJim Harris 240f634b4c1SWarner Losh if (arg_parse(argc, argv, f)) 241f634b4c1SWarner Losh return; 24249fac610SJim Harris 243f634b4c1SWarner Losh if (opt.slot == 0) { 24449fac610SJim Harris fprintf(stderr, 24549fac610SJim Harris "0 is not a valid slot number. " 24649fac610SJim Harris "Slot numbers start at 1.\n"); 247f634b4c1SWarner Losh arg_help(argc, argv, f); 248f634b4c1SWarner Losh } else if (opt.slot > 7 && opt.slot != NONE) { 24949fac610SJim Harris fprintf(stderr, 25049fac610SJim Harris "Slot number %s specified which is " 25149fac610SJim Harris "greater than max allowed slot number of " 25249fac610SJim Harris "7.\n", optarg); 253f634b4c1SWarner Losh arg_help(argc, argv, f); 25449fac610SJim Harris } 25549fac610SJim Harris 256f634b4c1SWarner Losh if (!opt.activate && opt.fw_img == NULL) { 25749fac610SJim Harris fprintf(stderr, 25849fac610SJim Harris "Neither a replace ([-f path_to_firmware]) nor " 25949fac610SJim Harris "activate ([-a]) firmware image action\n" 26049fac610SJim Harris "was specified.\n"); 261f634b4c1SWarner Losh arg_help(argc, argv, f); 26249fac610SJim Harris } 26349fac610SJim Harris 264f634b4c1SWarner Losh if (opt.activate && opt.fw_img == NULL && opt.slot == 0) { 26549fac610SJim Harris fprintf(stderr, 26649fac610SJim Harris "Slot number to activate not specified.\n"); 267f634b4c1SWarner Losh arg_help(argc, argv, f); 26849fac610SJim Harris } 26949fac610SJim Harris 270f634b4c1SWarner Losh open_dev(opt.dev, &fd, 1, 1); 2715458a1c8SAlexander Motin get_nsid(fd, &path, &nsid); 272a7bf63beSAlexander Motin if (nsid != 0) { 273a7bf63beSAlexander Motin close(fd); 2745458a1c8SAlexander Motin open_dev(path, &fd, 1, 1); 275a7bf63beSAlexander Motin } 2765458a1c8SAlexander Motin free(path); 277a7bf63beSAlexander Motin 2785dc463f9SAlexander Motin if (read_controller_data(fd, &cdata)) 2795dc463f9SAlexander Motin errx(EX_IOERR, "Identify request failed"); 28049fac610SJim Harris 281fba73a40SJohn Baldwin oacs_fw = NVMEV(NVME_CTRLR_DATA_OACS_FIRMWARE, cdata.oacs); 2820d787e9bSWojciech Macek 2830d787e9bSWojciech Macek if (oacs_fw == 0) 2845dc463f9SAlexander Motin errx(EX_UNAVAILABLE, 285821ef73cSJim Harris "controller does not support firmware activate/download"); 28649fac610SJim Harris 287fba73a40SJohn Baldwin fw_slot1_ro = NVMEV(NVME_CTRLR_DATA_FRMW_SLOT1_RO, cdata.frmw); 2880d787e9bSWojciech Macek 289f634b4c1SWarner Losh if (opt.fw_img && opt.slot == 1 && fw_slot1_ro) 2905dc463f9SAlexander Motin errx(EX_UNAVAILABLE, "slot %d is marked as read only", opt.slot); 29149fac610SJim Harris 292fba73a40SJohn Baldwin fw_num_slots = NVMEV(NVME_CTRLR_DATA_FRMW_NUM_SLOTS, cdata.frmw); 2930d787e9bSWojciech Macek 294f634b4c1SWarner Losh if (opt.slot > fw_num_slots) 2955dc463f9SAlexander Motin errx(EX_UNAVAILABLE, 296821ef73cSJim Harris "slot %d specified but controller only supports %d slots", 297f634b4c1SWarner Losh opt.slot, fw_num_slots); 29849fac610SJim Harris 299f634b4c1SWarner Losh if (opt.activate && opt.fw_img == NULL && 300f634b4c1SWarner Losh !slot_has_valid_firmware(fd, opt.slot)) 3015dc463f9SAlexander Motin errx(EX_UNAVAILABLE, 302821ef73cSJim Harris "slot %d does not contain valid firmware,\n" 303821ef73cSJim Harris "try 'nvmecontrol logpage -p 3 %s' to get a list " 304821ef73cSJim Harris "of available images\n", 305f634b4c1SWarner Losh opt.slot, opt.dev); 30649fac610SJim Harris 307f634b4c1SWarner Losh if (opt.fw_img) 308f634b4c1SWarner Losh read_image_file(opt.fw_img, &buf, &size); 309fdfa4d2dSJim Harris 310f634b4c1SWarner Losh if (opt.fw_img != NULL&& opt.activate) 31149fac610SJim Harris printf("You are about to download and activate " 31249fac610SJim Harris "firmware image (%s) to controller %s.\n" 31349fac610SJim Harris "This may damage your controller and/or " 31449fac610SJim Harris "overwrite an existing firmware image.\n", 315f634b4c1SWarner Losh opt.fw_img, opt.dev); 316f634b4c1SWarner Losh else if (opt.activate) 31749fac610SJim Harris printf("You are about to activate a new firmware " 31849fac610SJim Harris "image on controller %s.\n" 31949fac610SJim Harris "This may damage your controller.\n", 320f634b4c1SWarner Losh opt.dev); 321f634b4c1SWarner Losh else if (opt.fw_img != NULL) 32249fac610SJim Harris printf("You are about to download firmware image " 32349fac610SJim Harris "(%s) to controller %s.\n" 32449fac610SJim Harris "This may damage your controller and/or " 32549fac610SJim Harris "overwrite an existing firmware image.\n", 326f634b4c1SWarner Losh opt.fw_img, opt.dev); 32749fac610SJim Harris 32849fac610SJim Harris printf("Are you sure you want to continue? (yes/no) "); 32949fac610SJim Harris while (1) { 33049fac610SJim Harris fgets(prompt, sizeof(prompt), stdin); 33149fac610SJim Harris if (strncasecmp(prompt, "yes", 3) == 0) 33249fac610SJim Harris break; 33349fac610SJim Harris if (strncasecmp(prompt, "no", 2) == 0) 3345dc463f9SAlexander Motin exit(EX_DATAERR); 33549fac610SJim Harris printf("Please answer \"yes\" or \"no\". "); 33649fac610SJim Harris } 33749fac610SJim Harris 338f634b4c1SWarner Losh if (opt.fw_img != NULL) { 33916969d14SDavid Bright update_firmware(fd, buf, size, cdata.fwug); 340f634b4c1SWarner Losh if (opt.activate) 3414a14f9daSJim Harris activate_action = NVME_AA_REPLACE_ACTIVATE; 34249fac610SJim Harris else 3434a14f9daSJim Harris activate_action = NVME_AA_REPLACE_NO_ACTIVATE; 34449fac610SJim Harris } else { 3454a14f9daSJim Harris activate_action = NVME_AA_ACTIVATE; 34649fac610SJim Harris } 34749fac610SJim Harris 348f634b4c1SWarner Losh reboot_required = activate_firmware(fd, opt.slot, activate_action); 3494a14f9daSJim Harris 350f634b4c1SWarner Losh if (opt.activate) { 3514a14f9daSJim Harris if (reboot_required) { 3524a14f9daSJim Harris printf("New firmware image activated but requires " 3534a14f9daSJim Harris "conventional reset (i.e. reboot) to " 3544a14f9daSJim Harris "complete activation.\n"); 3554a14f9daSJim Harris } else { 35649fac610SJim Harris printf("New firmware image activated and will take " 35749fac610SJim Harris "effect after next controller reset.\n" 35849fac610SJim Harris "Controller reset can be initiated via " 35949fac610SJim Harris "'nvmecontrol reset %s'\n", 360f634b4c1SWarner Losh opt.dev); 36149fac610SJim Harris } 3624a14f9daSJim Harris } 36349fac610SJim Harris 36449fac610SJim Harris close(fd); 365821ef73cSJim Harris exit(0); 36649fac610SJim Harris } 367