149fac610SJim Harris /*- 21de7b4b8SPedro F. Giffuni * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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/cdefs.h> 3349fac610SJim Harris __FBSDID("$FreeBSD$"); 3449fac610SJim Harris 3549fac610SJim Harris #include <sys/param.h> 3649fac610SJim Harris #include <sys/ioccom.h> 3749fac610SJim Harris #include <sys/stat.h> 3849fac610SJim Harris #include <sys/types.h> 3949fac610SJim Harris 4049fac610SJim Harris #include <ctype.h> 41821ef73cSJim Harris #include <err.h> 4249fac610SJim Harris #include <fcntl.h> 43821ef73cSJim Harris #include <inttypes.h> 4449fac610SJim Harris #include <stdbool.h> 4549fac610SJim Harris #include <stddef.h> 4649fac610SJim Harris #include <stdio.h> 4749fac610SJim Harris #include <stdlib.h> 4849fac610SJim Harris #include <string.h> 4949fac610SJim Harris #include <unistd.h> 5049fac610SJim Harris 5149fac610SJim Harris #include "nvmecontrol.h" 5249fac610SJim Harris 53f634b4c1SWarner Losh /* Tables for command line parsing */ 54f634b4c1SWarner Losh 55f634b4c1SWarner Losh static cmd_fn_t firmware; 56f634b4c1SWarner Losh 57f634b4c1SWarner Losh #define NONE 0xffffffffu 58f634b4c1SWarner Losh static struct options { 59f634b4c1SWarner Losh bool activate; 60f634b4c1SWarner Losh uint32_t slot; 61f634b4c1SWarner Losh const char *fw_img; 62f634b4c1SWarner Losh const char *dev; 63f634b4c1SWarner Losh } opt = { 64f634b4c1SWarner Losh .activate = false, 65f634b4c1SWarner Losh .slot = NONE, 66f634b4c1SWarner Losh .fw_img = NULL, 67f634b4c1SWarner Losh .dev = NULL, 68f634b4c1SWarner Losh }; 69f634b4c1SWarner Losh 70f634b4c1SWarner Losh static const struct opts firmware_opts[] = { 71f634b4c1SWarner Losh #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } 72f634b4c1SWarner Losh OPT("activate", 'a', arg_none, opt, activate, 73f634b4c1SWarner Losh "Attempt to activate firmware"), 74f634b4c1SWarner Losh OPT("slot", 's', arg_uint32, opt, slot, 75f634b4c1SWarner Losh "Slot to activate and/or download firmware to"), 76f634b4c1SWarner Losh OPT("firmware", 'f', arg_path, opt, fw_img, 77f634b4c1SWarner Losh "Firmware image to download"), 78f634b4c1SWarner Losh { NULL, 0, arg_none, NULL, NULL } 79f634b4c1SWarner Losh }; 80f634b4c1SWarner Losh #undef OPT 81f634b4c1SWarner Losh 82f634b4c1SWarner Losh static const struct args firmware_args[] = { 83f634b4c1SWarner Losh { arg_string, &opt.dev, "controller-id" }, 84f634b4c1SWarner Losh { arg_none, NULL, NULL }, 85f634b4c1SWarner Losh }; 86f634b4c1SWarner Losh 87f634b4c1SWarner Losh static struct cmd firmware_cmd = { 88f634b4c1SWarner Losh .name = "firmware", 89f634b4c1SWarner Losh .fn = firmware, 90e843651bSAlexander Motin .descr = "Download firmware image to controller", 91f634b4c1SWarner Losh .ctx_size = sizeof(opt), 92f634b4c1SWarner Losh .opts = firmware_opts, 93f634b4c1SWarner Losh .args = firmware_args, 94f634b4c1SWarner Losh }; 95f634b4c1SWarner Losh 96f634b4c1SWarner Losh CMD_COMMAND(firmware_cmd); 97f634b4c1SWarner Losh 98f634b4c1SWarner Losh /* End of tables for command line parsing */ 99a13a291aSWarner Losh 10049fac610SJim Harris static int 10149fac610SJim Harris slot_has_valid_firmware(int fd, int slot) 10249fac610SJim Harris { 10349fac610SJim Harris struct nvme_firmware_page fw; 10449fac610SJim Harris int has_fw = false; 10549fac610SJim Harris 10649fac610SJim Harris read_logpage(fd, NVME_LOG_FIRMWARE_SLOT, 1076c99d132SAlexander Motin NVME_GLOBAL_NAMESPACE_TAG, 0, 0, 0, &fw, sizeof(fw)); 10849fac610SJim Harris 10949fac610SJim Harris if (fw.revision[slot-1] != 0LLU) 11049fac610SJim Harris has_fw = true; 11149fac610SJim Harris 11249fac610SJim Harris return (has_fw); 11349fac610SJim Harris } 11449fac610SJim Harris 11549fac610SJim Harris static void 116f634b4c1SWarner Losh read_image_file(const char *path, void **buf, int32_t *size) 11749fac610SJim Harris { 11849fac610SJim Harris struct stat sb; 119821ef73cSJim Harris int32_t filesize; 12049fac610SJim Harris int fd; 12149fac610SJim Harris 12249fac610SJim Harris *size = 0; 12349fac610SJim Harris *buf = NULL; 12449fac610SJim Harris 125821ef73cSJim Harris if ((fd = open(path, O_RDONLY)) < 0) 126821ef73cSJim Harris err(1, "unable to open '%s'", path); 127821ef73cSJim Harris if (fstat(fd, &sb) < 0) 128821ef73cSJim Harris err(1, "unable to stat '%s'", path); 129821ef73cSJim Harris 130821ef73cSJim Harris /* 131821ef73cSJim Harris * The NVMe spec does not explicitly state a maximum firmware image 132821ef73cSJim Harris * size, although one can be inferred from the dword size limitation 133821ef73cSJim Harris * for the size and offset fields in the Firmware Image Download 134821ef73cSJim Harris * command. 135821ef73cSJim Harris * 136821ef73cSJim Harris * Technically, the max is UINT32_MAX * sizeof(uint32_t), since the 137821ef73cSJim Harris * size and offsets are specified in terms of dwords (not bytes), but 138821ef73cSJim Harris * realistically INT32_MAX is sufficient here and simplifies matters 139821ef73cSJim Harris * a bit. 140821ef73cSJim Harris */ 141821ef73cSJim Harris if (sb.st_size > INT32_MAX) 142821ef73cSJim Harris errx(1, "size of file '%s' is too large (%jd bytes)", 143821ef73cSJim Harris path, (intmax_t)sb.st_size); 144821ef73cSJim Harris filesize = (int32_t)sb.st_size; 145821ef73cSJim Harris if ((*buf = malloc(filesize)) == NULL) 146008ac71eSJim Harris errx(1, "unable to malloc %d bytes", filesize); 147821ef73cSJim Harris if ((*size = read(fd, *buf, filesize)) < 0) 148821ef73cSJim Harris err(1, "error reading '%s'", path); 149821ef73cSJim Harris /* XXX assuming no short reads */ 150821ef73cSJim Harris if (*size != filesize) 151821ef73cSJim Harris errx(1, 152821ef73cSJim Harris "error reading '%s' (read %d bytes, requested %d bytes)", 153821ef73cSJim Harris path, *size, filesize); 154*6995fb5eSDavid Bright close(fd); 15549fac610SJim Harris } 15649fac610SJim Harris 15749fac610SJim Harris static void 158821ef73cSJim Harris update_firmware(int fd, uint8_t *payload, int32_t payload_size) 15949fac610SJim Harris { 16049fac610SJim Harris struct nvme_pt_command pt; 161821ef73cSJim Harris int32_t off, resid, size; 16249fac610SJim Harris void *chunk; 16349fac610SJim Harris 16449fac610SJim Harris off = 0; 16549fac610SJim Harris resid = payload_size; 16649fac610SJim Harris 1673f13e7a8SWarner Losh if ((chunk = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE)) == NULL) 168821ef73cSJim Harris errx(1, "unable to malloc %d bytes", NVME_MAX_XFER_SIZE); 16949fac610SJim Harris 17049fac610SJim Harris while (resid > 0) { 17149fac610SJim Harris size = (resid >= NVME_MAX_XFER_SIZE) ? 17249fac610SJim Harris NVME_MAX_XFER_SIZE : resid; 17349fac610SJim Harris memcpy(chunk, payload + off, size); 17449fac610SJim Harris 17549fac610SJim Harris memset(&pt, 0, sizeof(pt)); 1769544e6dcSChuck Tuffli pt.cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD; 1770d787e9bSWojciech Macek pt.cmd.cdw10 = htole32((size / sizeof(uint32_t)) - 1); 1780d787e9bSWojciech Macek pt.cmd.cdw11 = htole32(off / sizeof(uint32_t)); 17949fac610SJim Harris pt.buf = chunk; 18049fac610SJim Harris pt.len = size; 18149fac610SJim Harris pt.is_read = 0; 18249fac610SJim Harris 183821ef73cSJim Harris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 184821ef73cSJim Harris err(1, "firmware download request failed"); 18549fac610SJim Harris 186821ef73cSJim Harris if (nvme_completion_is_error(&pt.cpl)) 187821ef73cSJim Harris errx(1, "firmware download request returned error"); 18849fac610SJim Harris 18949fac610SJim Harris resid -= size; 19049fac610SJim Harris off += size; 19149fac610SJim Harris } 192*6995fb5eSDavid Bright free(chunk); 19349fac610SJim Harris } 19449fac610SJim Harris 1954a14f9daSJim Harris static int 19649fac610SJim Harris activate_firmware(int fd, int slot, int activate_action) 19749fac610SJim Harris { 19849fac610SJim Harris struct nvme_pt_command pt; 1990d787e9bSWojciech Macek uint16_t sct, sc; 20049fac610SJim Harris 20149fac610SJim Harris memset(&pt, 0, sizeof(pt)); 2029544e6dcSChuck Tuffli pt.cmd.opc = NVME_OPC_FIRMWARE_ACTIVATE; 2030d787e9bSWojciech Macek pt.cmd.cdw10 = htole32((activate_action << 3) | slot); 20449fac610SJim Harris pt.is_read = 0; 20549fac610SJim Harris 206821ef73cSJim Harris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 207821ef73cSJim Harris err(1, "firmware activate request failed"); 20849fac610SJim Harris 2090d787e9bSWojciech Macek sct = NVME_STATUS_GET_SCT(pt.cpl.status); 2100d787e9bSWojciech Macek sc = NVME_STATUS_GET_SC(pt.cpl.status); 2110d787e9bSWojciech Macek 2120d787e9bSWojciech Macek if (sct == NVME_SCT_COMMAND_SPECIFIC && 2130d787e9bSWojciech Macek sc == NVME_SC_FIRMWARE_REQUIRES_RESET) 2144a14f9daSJim Harris return 1; 2154a14f9daSJim Harris 216821ef73cSJim Harris if (nvme_completion_is_error(&pt.cpl)) 217821ef73cSJim Harris errx(1, "firmware activate request returned error"); 2184a14f9daSJim Harris 2194a14f9daSJim Harris return 0; 22049fac610SJim Harris } 22149fac610SJim Harris 22249fac610SJim Harris static void 223f634b4c1SWarner Losh firmware(const struct cmd *f, int argc, char *argv[]) 22449fac610SJim Harris { 225f634b4c1SWarner Losh int fd = -1; 2264a14f9daSJim Harris int activate_action, reboot_required; 227f634b4c1SWarner Losh char prompt[64]; 22849fac610SJim Harris void *buf = NULL; 229a7bf63beSAlexander Motin int32_t size = 0, nsid; 2300d787e9bSWojciech Macek uint16_t oacs_fw; 2310d787e9bSWojciech Macek uint8_t fw_slot1_ro, fw_num_slots; 23249fac610SJim Harris struct nvme_controller_data cdata; 23349fac610SJim Harris 234f634b4c1SWarner Losh if (arg_parse(argc, argv, f)) 235f634b4c1SWarner Losh return; 23649fac610SJim Harris 237f634b4c1SWarner Losh if (opt.slot == 0) { 23849fac610SJim Harris fprintf(stderr, 23949fac610SJim Harris "0 is not a valid slot number. " 24049fac610SJim Harris "Slot numbers start at 1.\n"); 241f634b4c1SWarner Losh arg_help(argc, argv, f); 242f634b4c1SWarner Losh } else if (opt.slot > 7 && opt.slot != NONE) { 24349fac610SJim Harris fprintf(stderr, 24449fac610SJim Harris "Slot number %s specified which is " 24549fac610SJim Harris "greater than max allowed slot number of " 24649fac610SJim Harris "7.\n", optarg); 247f634b4c1SWarner Losh arg_help(argc, argv, f); 24849fac610SJim Harris } 24949fac610SJim Harris 250f634b4c1SWarner Losh if (!opt.activate && opt.fw_img == NULL) { 25149fac610SJim Harris fprintf(stderr, 25249fac610SJim Harris "Neither a replace ([-f path_to_firmware]) nor " 25349fac610SJim Harris "activate ([-a]) firmware image action\n" 25449fac610SJim Harris "was specified.\n"); 255f634b4c1SWarner Losh arg_help(argc, argv, f); 25649fac610SJim Harris } 25749fac610SJim Harris 258f634b4c1SWarner Losh if (opt.activate && opt.fw_img == NULL && opt.slot == 0) { 25949fac610SJim Harris fprintf(stderr, 26049fac610SJim Harris "Slot number to activate not specified.\n"); 261f634b4c1SWarner Losh arg_help(argc, argv, f); 26249fac610SJim Harris } 26349fac610SJim Harris 264f634b4c1SWarner Losh open_dev(opt.dev, &fd, 1, 1); 265a7bf63beSAlexander Motin 266a7bf63beSAlexander Motin /* Check that a controller (and not a namespace) was specified. */ 267a7bf63beSAlexander Motin get_nsid(fd, NULL, &nsid); 268a7bf63beSAlexander Motin if (nsid != 0) { 269a7bf63beSAlexander Motin close(fd); 270a7bf63beSAlexander Motin arg_help(argc, argv, f); 271a7bf63beSAlexander Motin } 272a7bf63beSAlexander Motin 27349fac610SJim Harris read_controller_data(fd, &cdata); 27449fac610SJim Harris 2750d787e9bSWojciech Macek oacs_fw = (cdata.oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) & 2760d787e9bSWojciech Macek NVME_CTRLR_DATA_OACS_FIRMWARE_MASK; 2770d787e9bSWojciech Macek 2780d787e9bSWojciech Macek if (oacs_fw == 0) 279821ef73cSJim Harris errx(1, 280821ef73cSJim Harris "controller does not support firmware activate/download"); 28149fac610SJim Harris 2820d787e9bSWojciech Macek fw_slot1_ro = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_SLOT1_RO_SHIFT) & 2830d787e9bSWojciech Macek NVME_CTRLR_DATA_FRMW_SLOT1_RO_MASK; 2840d787e9bSWojciech Macek 285f634b4c1SWarner Losh if (opt.fw_img && opt.slot == 1 && fw_slot1_ro) 286f634b4c1SWarner Losh errx(1, "slot %d is marked as read only", opt.slot); 28749fac610SJim Harris 2880d787e9bSWojciech Macek fw_num_slots = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_NUM_SLOTS_SHIFT) & 2890d787e9bSWojciech Macek NVME_CTRLR_DATA_FRMW_NUM_SLOTS_MASK; 2900d787e9bSWojciech Macek 291f634b4c1SWarner Losh if (opt.slot > fw_num_slots) 292821ef73cSJim Harris errx(1, 293821ef73cSJim Harris "slot %d specified but controller only supports %d slots", 294f634b4c1SWarner Losh opt.slot, fw_num_slots); 29549fac610SJim Harris 296f634b4c1SWarner Losh if (opt.activate && opt.fw_img == NULL && 297f634b4c1SWarner Losh !slot_has_valid_firmware(fd, opt.slot)) 298821ef73cSJim Harris errx(1, 299821ef73cSJim Harris "slot %d does not contain valid firmware,\n" 300821ef73cSJim Harris "try 'nvmecontrol logpage -p 3 %s' to get a list " 301821ef73cSJim Harris "of available images\n", 302f634b4c1SWarner Losh opt.slot, opt.dev); 30349fac610SJim Harris 304f634b4c1SWarner Losh if (opt.fw_img) 305f634b4c1SWarner Losh read_image_file(opt.fw_img, &buf, &size); 306fdfa4d2dSJim Harris 307f634b4c1SWarner Losh if (opt.fw_img != NULL&& opt.activate) 30849fac610SJim Harris printf("You are about to download and activate " 30949fac610SJim Harris "firmware image (%s) to controller %s.\n" 31049fac610SJim Harris "This may damage your controller and/or " 31149fac610SJim Harris "overwrite an existing firmware image.\n", 312f634b4c1SWarner Losh opt.fw_img, opt.dev); 313f634b4c1SWarner Losh else if (opt.activate) 31449fac610SJim Harris printf("You are about to activate a new firmware " 31549fac610SJim Harris "image on controller %s.\n" 31649fac610SJim Harris "This may damage your controller.\n", 317f634b4c1SWarner Losh opt.dev); 318f634b4c1SWarner Losh else if (opt.fw_img != NULL) 31949fac610SJim Harris printf("You are about to download firmware image " 32049fac610SJim Harris "(%s) to controller %s.\n" 32149fac610SJim Harris "This may damage your controller and/or " 32249fac610SJim Harris "overwrite an existing firmware image.\n", 323f634b4c1SWarner Losh opt.fw_img, opt.dev); 32449fac610SJim Harris 32549fac610SJim Harris printf("Are you sure you want to continue? (yes/no) "); 32649fac610SJim Harris while (1) { 32749fac610SJim Harris fgets(prompt, sizeof(prompt), stdin); 32849fac610SJim Harris if (strncasecmp(prompt, "yes", 3) == 0) 32949fac610SJim Harris break; 33049fac610SJim Harris if (strncasecmp(prompt, "no", 2) == 0) 331821ef73cSJim Harris exit(1); 33249fac610SJim Harris printf("Please answer \"yes\" or \"no\". "); 33349fac610SJim Harris } 33449fac610SJim Harris 335f634b4c1SWarner Losh if (opt.fw_img != NULL) { 33649fac610SJim Harris update_firmware(fd, buf, size); 337f634b4c1SWarner Losh if (opt.activate) 3384a14f9daSJim Harris activate_action = NVME_AA_REPLACE_ACTIVATE; 33949fac610SJim Harris else 3404a14f9daSJim Harris activate_action = NVME_AA_REPLACE_NO_ACTIVATE; 34149fac610SJim Harris } else { 3424a14f9daSJim Harris activate_action = NVME_AA_ACTIVATE; 34349fac610SJim Harris } 34449fac610SJim Harris 345f634b4c1SWarner Losh reboot_required = activate_firmware(fd, opt.slot, activate_action); 3464a14f9daSJim Harris 347f634b4c1SWarner Losh if (opt.activate) { 3484a14f9daSJim Harris if (reboot_required) { 3494a14f9daSJim Harris printf("New firmware image activated but requires " 3504a14f9daSJim Harris "conventional reset (i.e. reboot) to " 3514a14f9daSJim Harris "complete activation.\n"); 3524a14f9daSJim Harris } else { 35349fac610SJim Harris printf("New firmware image activated and will take " 35449fac610SJim Harris "effect after next controller reset.\n" 35549fac610SJim Harris "Controller reset can be initiated via " 35649fac610SJim Harris "'nvmecontrol reset %s'\n", 357f634b4c1SWarner Losh opt.dev); 35849fac610SJim Harris } 3594a14f9daSJim Harris } 36049fac610SJim Harris 36149fac610SJim Harris close(fd); 362821ef73cSJim Harris exit(0); 36349fac610SJim Harris } 364