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> 495dc463f9SAlexander Motin #include <sysexits.h> 5049fac610SJim Harris #include <unistd.h> 5149fac610SJim Harris 5249fac610SJim Harris #include "nvmecontrol.h" 5349fac610SJim Harris 54f634b4c1SWarner Losh /* Tables for command line parsing */ 55f634b4c1SWarner Losh 56f634b4c1SWarner Losh static cmd_fn_t firmware; 57f634b4c1SWarner Losh 58f634b4c1SWarner Losh #define NONE 0xffffffffu 59f634b4c1SWarner Losh static struct options { 60f634b4c1SWarner Losh bool activate; 61f634b4c1SWarner Losh uint32_t slot; 62f634b4c1SWarner Losh const char *fw_img; 63f634b4c1SWarner Losh const char *dev; 64f634b4c1SWarner Losh } opt = { 65f634b4c1SWarner Losh .activate = false, 66f634b4c1SWarner Losh .slot = NONE, 67f634b4c1SWarner Losh .fw_img = NULL, 68f634b4c1SWarner Losh .dev = NULL, 69f634b4c1SWarner Losh }; 70f634b4c1SWarner Losh 71f634b4c1SWarner Losh static const struct opts firmware_opts[] = { 72f634b4c1SWarner Losh #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } 73f634b4c1SWarner Losh OPT("activate", 'a', arg_none, opt, activate, 74f634b4c1SWarner Losh "Attempt to activate firmware"), 75f634b4c1SWarner Losh OPT("slot", 's', arg_uint32, opt, slot, 76f634b4c1SWarner Losh "Slot to activate and/or download firmware to"), 77f634b4c1SWarner Losh OPT("firmware", 'f', arg_path, opt, fw_img, 78f634b4c1SWarner Losh "Firmware image to download"), 79f634b4c1SWarner Losh { NULL, 0, arg_none, NULL, NULL } 80f634b4c1SWarner Losh }; 81f634b4c1SWarner Losh #undef OPT 82f634b4c1SWarner Losh 83f634b4c1SWarner Losh static const struct args firmware_args[] = { 845458a1c8SAlexander Motin { arg_string, &opt.dev, "controller-id|namespace-id" }, 85f634b4c1SWarner Losh { arg_none, NULL, NULL }, 86f634b4c1SWarner Losh }; 87f634b4c1SWarner Losh 88f634b4c1SWarner Losh static struct cmd firmware_cmd = { 89f634b4c1SWarner Losh .name = "firmware", 90f634b4c1SWarner Losh .fn = firmware, 91e843651bSAlexander Motin .descr = "Download firmware image to controller", 92f634b4c1SWarner Losh .ctx_size = sizeof(opt), 93f634b4c1SWarner Losh .opts = firmware_opts, 94f634b4c1SWarner Losh .args = firmware_args, 95f634b4c1SWarner Losh }; 96f634b4c1SWarner Losh 97f634b4c1SWarner Losh CMD_COMMAND(firmware_cmd); 98f634b4c1SWarner Losh 99f634b4c1SWarner Losh /* End of tables for command line parsing */ 100a13a291aSWarner Losh 10149fac610SJim Harris static int 10249fac610SJim Harris slot_has_valid_firmware(int fd, int slot) 10349fac610SJim Harris { 10449fac610SJim Harris struct nvme_firmware_page fw; 10549fac610SJim Harris int has_fw = false; 10649fac610SJim Harris 10749fac610SJim Harris read_logpage(fd, NVME_LOG_FIRMWARE_SLOT, 1086c99d132SAlexander Motin NVME_GLOBAL_NAMESPACE_TAG, 0, 0, 0, &fw, sizeof(fw)); 10949fac610SJim Harris 11049fac610SJim Harris if (fw.revision[slot-1] != 0LLU) 11149fac610SJim Harris has_fw = true; 11249fac610SJim Harris 11349fac610SJim Harris return (has_fw); 11449fac610SJim Harris } 11549fac610SJim Harris 11649fac610SJim Harris static void 117f634b4c1SWarner Losh read_image_file(const char *path, void **buf, int32_t *size) 11849fac610SJim Harris { 11949fac610SJim Harris struct stat sb; 120821ef73cSJim Harris int32_t filesize; 12149fac610SJim Harris int fd; 12249fac610SJim Harris 12349fac610SJim Harris *size = 0; 12449fac610SJim Harris *buf = NULL; 12549fac610SJim Harris 126821ef73cSJim Harris if ((fd = open(path, O_RDONLY)) < 0) 1275dc463f9SAlexander Motin err(EX_NOINPUT, "unable to open '%s'", path); 128821ef73cSJim Harris if (fstat(fd, &sb) < 0) 1295dc463f9SAlexander Motin err(EX_NOINPUT, "unable to stat '%s'", path); 130821ef73cSJim Harris 131821ef73cSJim Harris /* 132821ef73cSJim Harris * The NVMe spec does not explicitly state a maximum firmware image 133821ef73cSJim Harris * size, although one can be inferred from the dword size limitation 134821ef73cSJim Harris * for the size and offset fields in the Firmware Image Download 135821ef73cSJim Harris * command. 136821ef73cSJim Harris * 137821ef73cSJim Harris * Technically, the max is UINT32_MAX * sizeof(uint32_t), since the 138821ef73cSJim Harris * size and offsets are specified in terms of dwords (not bytes), but 139821ef73cSJim Harris * realistically INT32_MAX is sufficient here and simplifies matters 140821ef73cSJim Harris * a bit. 141821ef73cSJim Harris */ 142821ef73cSJim Harris if (sb.st_size > INT32_MAX) 1435dc463f9SAlexander Motin errx(EX_USAGE, "size of file '%s' is too large (%jd bytes)", 144821ef73cSJim Harris path, (intmax_t)sb.st_size); 145821ef73cSJim Harris filesize = (int32_t)sb.st_size; 146821ef73cSJim Harris if ((*buf = malloc(filesize)) == NULL) 1475dc463f9SAlexander Motin errx(EX_OSERR, "unable to malloc %d bytes", filesize); 148821ef73cSJim Harris if ((*size = read(fd, *buf, filesize)) < 0) 1495dc463f9SAlexander Motin err(EX_IOERR, "error reading '%s'", path); 150821ef73cSJim Harris /* XXX assuming no short reads */ 151821ef73cSJim Harris if (*size != filesize) 1525dc463f9SAlexander Motin errx(EX_IOERR, 153821ef73cSJim Harris "error reading '%s' (read %d bytes, requested %d bytes)", 154821ef73cSJim Harris path, *size, filesize); 1556995fb5eSDavid Bright close(fd); 15649fac610SJim Harris } 15749fac610SJim Harris 15849fac610SJim Harris static void 15916969d14SDavid Bright update_firmware(int fd, uint8_t *payload, int32_t payload_size, uint8_t fwug) 16049fac610SJim Harris { 16149fac610SJim Harris struct nvme_pt_command pt; 16216969d14SDavid Bright uint64_t max_xfer_size; 163821ef73cSJim Harris int32_t off, resid, size; 16449fac610SJim Harris void *chunk; 16549fac610SJim Harris 16649fac610SJim Harris off = 0; 16749fac610SJim Harris resid = payload_size; 16849fac610SJim Harris 169*329327e2SAlexander Motin if (ioctl(fd, NVME_GET_MAX_XFER_SIZE, &max_xfer_size) < 0) 1705dc463f9SAlexander Motin err(EX_IOERR, "query max transfer size failed"); 171*329327e2SAlexander Motin if (fwug != 0 && fwug != 0xFF) 172*329327e2SAlexander Motin max_xfer_size = MIN(max_xfer_size, (uint64_t)fwug << 12); 17316969d14SDavid Bright 17416969d14SDavid Bright if ((chunk = aligned_alloc(PAGE_SIZE, max_xfer_size)) == NULL) 1755dc463f9SAlexander Motin errx(EX_OSERR, "unable to malloc %zd bytes", (size_t)max_xfer_size); 17649fac610SJim Harris 17749fac610SJim Harris while (resid > 0) { 17816969d14SDavid Bright size = (resid >= (int32_t)max_xfer_size) ? 17916969d14SDavid Bright max_xfer_size : resid; 18049fac610SJim Harris memcpy(chunk, payload + off, size); 18149fac610SJim Harris 18249fac610SJim Harris memset(&pt, 0, sizeof(pt)); 1839544e6dcSChuck Tuffli pt.cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD; 1840d787e9bSWojciech Macek pt.cmd.cdw10 = htole32((size / sizeof(uint32_t)) - 1); 1850d787e9bSWojciech Macek pt.cmd.cdw11 = htole32(off / sizeof(uint32_t)); 18649fac610SJim Harris pt.buf = chunk; 18749fac610SJim Harris pt.len = size; 18849fac610SJim Harris pt.is_read = 0; 18949fac610SJim Harris 190821ef73cSJim Harris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 1915dc463f9SAlexander Motin err(EX_IOERR, "firmware download request failed"); 19249fac610SJim Harris 193821ef73cSJim Harris if (nvme_completion_is_error(&pt.cpl)) 1945dc463f9SAlexander Motin errx(EX_IOERR, "firmware download request returned error"); 19549fac610SJim Harris 19649fac610SJim Harris resid -= size; 19749fac610SJim Harris off += size; 19849fac610SJim Harris } 1996995fb5eSDavid Bright free(chunk); 20049fac610SJim Harris } 20149fac610SJim Harris 2024a14f9daSJim Harris static int 20349fac610SJim Harris activate_firmware(int fd, int slot, int activate_action) 20449fac610SJim Harris { 20549fac610SJim Harris struct nvme_pt_command pt; 2060d787e9bSWojciech Macek uint16_t sct, sc; 20749fac610SJim Harris 20849fac610SJim Harris memset(&pt, 0, sizeof(pt)); 2099544e6dcSChuck Tuffli pt.cmd.opc = NVME_OPC_FIRMWARE_ACTIVATE; 2100d787e9bSWojciech Macek pt.cmd.cdw10 = htole32((activate_action << 3) | slot); 21149fac610SJim Harris pt.is_read = 0; 21249fac610SJim Harris 213821ef73cSJim Harris if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) 2145dc463f9SAlexander Motin err(EX_IOERR, "firmware activate request failed"); 21549fac610SJim Harris 2160d787e9bSWojciech Macek sct = NVME_STATUS_GET_SCT(pt.cpl.status); 2170d787e9bSWojciech Macek sc = NVME_STATUS_GET_SC(pt.cpl.status); 2180d787e9bSWojciech Macek 2190d787e9bSWojciech Macek if (sct == NVME_SCT_COMMAND_SPECIFIC && 2200d787e9bSWojciech Macek sc == NVME_SC_FIRMWARE_REQUIRES_RESET) 2214a14f9daSJim Harris return 1; 2224a14f9daSJim Harris 223821ef73cSJim Harris if (nvme_completion_is_error(&pt.cpl)) 2245dc463f9SAlexander Motin errx(EX_IOERR, "firmware activate request returned error"); 2254a14f9daSJim Harris 2264a14f9daSJim Harris return 0; 22749fac610SJim Harris } 22849fac610SJim Harris 22949fac610SJim Harris static void 230f634b4c1SWarner Losh firmware(const struct cmd *f, int argc, char *argv[]) 23149fac610SJim Harris { 232f634b4c1SWarner Losh int fd = -1; 2334a14f9daSJim Harris int activate_action, reboot_required; 234f634b4c1SWarner Losh char prompt[64]; 23549fac610SJim Harris void *buf = NULL; 2365458a1c8SAlexander Motin char *path; 237a7bf63beSAlexander Motin int32_t size = 0, nsid; 2380d787e9bSWojciech Macek uint16_t oacs_fw; 2390d787e9bSWojciech Macek uint8_t fw_slot1_ro, fw_num_slots; 24049fac610SJim Harris struct nvme_controller_data cdata; 24149fac610SJim Harris 242f634b4c1SWarner Losh if (arg_parse(argc, argv, f)) 243f634b4c1SWarner Losh return; 24449fac610SJim Harris 245f634b4c1SWarner Losh if (opt.slot == 0) { 24649fac610SJim Harris fprintf(stderr, 24749fac610SJim Harris "0 is not a valid slot number. " 24849fac610SJim Harris "Slot numbers start at 1.\n"); 249f634b4c1SWarner Losh arg_help(argc, argv, f); 250f634b4c1SWarner Losh } else if (opt.slot > 7 && opt.slot != NONE) { 25149fac610SJim Harris fprintf(stderr, 25249fac610SJim Harris "Slot number %s specified which is " 25349fac610SJim Harris "greater than max allowed slot number of " 25449fac610SJim Harris "7.\n", optarg); 255f634b4c1SWarner Losh arg_help(argc, argv, f); 25649fac610SJim Harris } 25749fac610SJim Harris 258f634b4c1SWarner Losh if (!opt.activate && opt.fw_img == NULL) { 25949fac610SJim Harris fprintf(stderr, 26049fac610SJim Harris "Neither a replace ([-f path_to_firmware]) nor " 26149fac610SJim Harris "activate ([-a]) firmware image action\n" 26249fac610SJim Harris "was specified.\n"); 263f634b4c1SWarner Losh arg_help(argc, argv, f); 26449fac610SJim Harris } 26549fac610SJim Harris 266f634b4c1SWarner Losh if (opt.activate && opt.fw_img == NULL && opt.slot == 0) { 26749fac610SJim Harris fprintf(stderr, 26849fac610SJim Harris "Slot number to activate not specified.\n"); 269f634b4c1SWarner Losh arg_help(argc, argv, f); 27049fac610SJim Harris } 27149fac610SJim Harris 272f634b4c1SWarner Losh open_dev(opt.dev, &fd, 1, 1); 2735458a1c8SAlexander Motin get_nsid(fd, &path, &nsid); 274a7bf63beSAlexander Motin if (nsid != 0) { 275a7bf63beSAlexander Motin close(fd); 2765458a1c8SAlexander Motin open_dev(path, &fd, 1, 1); 277a7bf63beSAlexander Motin } 2785458a1c8SAlexander Motin free(path); 279a7bf63beSAlexander Motin 2805dc463f9SAlexander Motin if (read_controller_data(fd, &cdata)) 2815dc463f9SAlexander Motin errx(EX_IOERR, "Identify request failed"); 28249fac610SJim Harris 2830d787e9bSWojciech Macek oacs_fw = (cdata.oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) & 2840d787e9bSWojciech Macek NVME_CTRLR_DATA_OACS_FIRMWARE_MASK; 2850d787e9bSWojciech Macek 2860d787e9bSWojciech Macek if (oacs_fw == 0) 2875dc463f9SAlexander Motin errx(EX_UNAVAILABLE, 288821ef73cSJim Harris "controller does not support firmware activate/download"); 28949fac610SJim Harris 2900d787e9bSWojciech Macek fw_slot1_ro = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_SLOT1_RO_SHIFT) & 2910d787e9bSWojciech Macek NVME_CTRLR_DATA_FRMW_SLOT1_RO_MASK; 2920d787e9bSWojciech Macek 293f634b4c1SWarner Losh if (opt.fw_img && opt.slot == 1 && fw_slot1_ro) 2945dc463f9SAlexander Motin errx(EX_UNAVAILABLE, "slot %d is marked as read only", opt.slot); 29549fac610SJim Harris 2960d787e9bSWojciech Macek fw_num_slots = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_NUM_SLOTS_SHIFT) & 2970d787e9bSWojciech Macek NVME_CTRLR_DATA_FRMW_NUM_SLOTS_MASK; 2980d787e9bSWojciech Macek 299f634b4c1SWarner Losh if (opt.slot > fw_num_slots) 3005dc463f9SAlexander Motin errx(EX_UNAVAILABLE, 301821ef73cSJim Harris "slot %d specified but controller only supports %d slots", 302f634b4c1SWarner Losh opt.slot, fw_num_slots); 30349fac610SJim Harris 304f634b4c1SWarner Losh if (opt.activate && opt.fw_img == NULL && 305f634b4c1SWarner Losh !slot_has_valid_firmware(fd, opt.slot)) 3065dc463f9SAlexander Motin errx(EX_UNAVAILABLE, 307821ef73cSJim Harris "slot %d does not contain valid firmware,\n" 308821ef73cSJim Harris "try 'nvmecontrol logpage -p 3 %s' to get a list " 309821ef73cSJim Harris "of available images\n", 310f634b4c1SWarner Losh opt.slot, opt.dev); 31149fac610SJim Harris 312f634b4c1SWarner Losh if (opt.fw_img) 313f634b4c1SWarner Losh read_image_file(opt.fw_img, &buf, &size); 314fdfa4d2dSJim Harris 315f634b4c1SWarner Losh if (opt.fw_img != NULL&& opt.activate) 31649fac610SJim Harris printf("You are about to download and activate " 31749fac610SJim Harris "firmware image (%s) to controller %s.\n" 31849fac610SJim Harris "This may damage your controller and/or " 31949fac610SJim Harris "overwrite an existing firmware image.\n", 320f634b4c1SWarner Losh opt.fw_img, opt.dev); 321f634b4c1SWarner Losh else if (opt.activate) 32249fac610SJim Harris printf("You are about to activate a new firmware " 32349fac610SJim Harris "image on controller %s.\n" 32449fac610SJim Harris "This may damage your controller.\n", 325f634b4c1SWarner Losh opt.dev); 326f634b4c1SWarner Losh else if (opt.fw_img != NULL) 32749fac610SJim Harris printf("You are about to download firmware image " 32849fac610SJim Harris "(%s) to controller %s.\n" 32949fac610SJim Harris "This may damage your controller and/or " 33049fac610SJim Harris "overwrite an existing firmware image.\n", 331f634b4c1SWarner Losh opt.fw_img, opt.dev); 33249fac610SJim Harris 33349fac610SJim Harris printf("Are you sure you want to continue? (yes/no) "); 33449fac610SJim Harris while (1) { 33549fac610SJim Harris fgets(prompt, sizeof(prompt), stdin); 33649fac610SJim Harris if (strncasecmp(prompt, "yes", 3) == 0) 33749fac610SJim Harris break; 33849fac610SJim Harris if (strncasecmp(prompt, "no", 2) == 0) 3395dc463f9SAlexander Motin exit(EX_DATAERR); 34049fac610SJim Harris printf("Please answer \"yes\" or \"no\". "); 34149fac610SJim Harris } 34249fac610SJim Harris 343f634b4c1SWarner Losh if (opt.fw_img != NULL) { 34416969d14SDavid Bright update_firmware(fd, buf, size, cdata.fwug); 345f634b4c1SWarner Losh if (opt.activate) 3464a14f9daSJim Harris activate_action = NVME_AA_REPLACE_ACTIVATE; 34749fac610SJim Harris else 3484a14f9daSJim Harris activate_action = NVME_AA_REPLACE_NO_ACTIVATE; 34949fac610SJim Harris } else { 3504a14f9daSJim Harris activate_action = NVME_AA_ACTIVATE; 35149fac610SJim Harris } 35249fac610SJim Harris 353f634b4c1SWarner Losh reboot_required = activate_firmware(fd, opt.slot, activate_action); 3544a14f9daSJim Harris 355f634b4c1SWarner Losh if (opt.activate) { 3564a14f9daSJim Harris if (reboot_required) { 3574a14f9daSJim Harris printf("New firmware image activated but requires " 3584a14f9daSJim Harris "conventional reset (i.e. reboot) to " 3594a14f9daSJim Harris "complete activation.\n"); 3604a14f9daSJim Harris } else { 36149fac610SJim Harris printf("New firmware image activated and will take " 36249fac610SJim Harris "effect after next controller reset.\n" 36349fac610SJim Harris "Controller reset can be initiated via " 36449fac610SJim Harris "'nvmecontrol reset %s'\n", 365f634b4c1SWarner Losh opt.dev); 36649fac610SJim Harris } 3674a14f9daSJim Harris } 36849fac610SJim Harris 36949fac610SJim Harris close(fd); 370821ef73cSJim Harris exit(0); 37149fac610SJim Harris } 372