diff options
Diffstat (limited to 'usr/src')
-rw-r--r-- | usr/src/uts/common/io/nvme/nvme.c | 150 | ||||
-rw-r--r-- | usr/src/uts/common/io/nvme/nvme_var.h | 9 | ||||
-rw-r--r-- | usr/src/uts/common/os/ddi_ufm.c | 5 | ||||
-rw-r--r-- | usr/src/uts/common/sys/nvme.h | 15 |
4 files changed, 174 insertions, 5 deletions
diff --git a/usr/src/uts/common/io/nvme/nvme.c b/usr/src/uts/common/io/nvme/nvme.c index 5af89e3874..b77545ead2 100644 --- a/usr/src/uts/common/io/nvme/nvme.c +++ b/usr/src/uts/common/io/nvme/nvme.c @@ -201,6 +201,14 @@ * device. * * + * DDI UFM Support + * + * The driver supports the DDI UFM framework for reporting information about + * the device's firmware image and slot configuration. This data can be + * queried by userland software via ioctls to the ufm driver. For more + * information, see ddi_ufm(9E). + * + * * Driver Configuration: * * The following driver properties can be changed to control some aspects of the @@ -247,6 +255,7 @@ #include <sys/conf.h> #include <sys/devops.h> #include <sys/ddi.h> +#include <sys/ddi_ufm.h> #include <sys/sunddi.h> #include <sys/sunndi.h> #include <sys/bitmap.h> @@ -386,10 +395,24 @@ static void nvme_prp_dma_destructor(void *, void *); static void nvme_prepare_devid(nvme_t *, uint32_t); +/* DDI UFM callbacks */ +static int nvme_ufm_fill_image(ddi_ufm_handle_t *, void *, uint_t, + ddi_ufm_image_t *); +static int nvme_ufm_fill_slot(ddi_ufm_handle_t *, void *, uint_t, uint_t, + ddi_ufm_slot_t *); +static int nvme_ufm_getcaps(ddi_ufm_handle_t *, void *, ddi_ufm_cap_t *); + static int nvme_open(dev_t *, int, int, cred_t *); static int nvme_close(dev_t, int, int, cred_t *); static int nvme_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); +static ddi_ufm_ops_t nvme_ufm_ops = { + NULL, + nvme_ufm_fill_image, + nvme_ufm_fill_slot, + nvme_ufm_getcaps +}; + #define NVME_MINOR_INST_SHIFT 9 #define NVME_MINOR(inst, nsid) (((inst) << NVME_MINOR_INST_SHIFT) | (nsid)) #define NVME_MINOR_INST(minor) ((minor) >> NVME_MINOR_INST_SHIFT) @@ -3352,6 +3375,18 @@ nvme_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) goto fail; /* + * Initialize the driver with the UFM subsystem + */ + if (ddi_ufm_init(dip, DDI_UFM_CURRENT_VERSION, &nvme_ufm_ops, + &nvme->n_ufmh, nvme) != 0) { + dev_err(dip, CE_WARN, "!failed to initialize UFM subsystem"); + goto fail; + } + mutex_init(&nvme->n_fwslot_mutex, NULL, MUTEX_DRIVER, NULL); + ddi_ufm_update(nvme->n_ufmh); + nvme->n_progress |= NVME_UFM_INIT; + + /* * Attach the blkdev driver for each namespace. */ for (i = 0; i != nvme->n_namespace_count; i++) { @@ -3444,6 +3479,10 @@ nvme_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) kmem_free(nvme->n_ns, sizeof (nvme_namespace_t) * nvme->n_namespace_count); } + if (nvme->n_progress & NVME_UFM_INIT) { + ddi_ufm_fini(nvme->n_ufmh); + mutex_destroy(&nvme->n_fwslot_mutex); + } if (nvme->n_progress & NVME_INTERRUPTS) nvme_release_interrupts(nvme); @@ -4354,6 +4393,18 @@ nvme_ioctl_attach(nvme_t *nvme, int nsid, nvme_ioctl_t *nioc, int mode, return (rv); } +static void +nvme_ufm_update(nvme_t *nvme) +{ + mutex_enter(&nvme->n_fwslot_mutex); + ddi_ufm_update(nvme->n_ufmh); + if (nvme->n_fwslot != NULL) { + kmem_free(nvme->n_fwslot, sizeof (nvme_fwslot_log_t)); + nvme->n_fwslot = NULL; + } + mutex_exit(&nvme->n_fwslot_mutex); +} + static int nvme_ioctl_firmware_download(nvme_t *nvme, int nsid, nvme_ioctl_t *nioc, int mode, cred_t *cred_p) @@ -4406,6 +4457,12 @@ nvme_ioctl_firmware_download(nvme_t *nvme, int nsid, nvme_ioctl_t *nioc, len -= copylen; } + /* + * Let the DDI UFM subsystem know that the firmware information for + * this device has changed. + */ + nvme_ufm_update(nvme); + return (rv); } @@ -4454,6 +4511,12 @@ nvme_ioctl_firmware_commit(nvme_t *nvme, int nsid, nvme_ioctl_t *nioc, nioc->n_arg = ((uint64_t)cqe.cqe_sf.sf_sct << 16) | cqe.cqe_sf.sf_sc; + /* + * Let the DDI UFM subsystem know that the firmware information for + * this device has changed. + */ + nvme_ufm_update(nvme); + return (rv); } @@ -4567,3 +4630,90 @@ nvme_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred_p, return (rv); } + +/* + * DDI UFM Callbacks + */ +static int +nvme_ufm_fill_image(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno, + ddi_ufm_image_t *img) +{ + nvme_t *nvme = arg; + + if (imgno != 0) + return (EINVAL); + + ddi_ufm_image_set_desc(img, "Firmware"); + ddi_ufm_image_set_nslots(img, nvme->n_idctl->id_frmw.fw_nslot); + + return (0); +} + +/* + * Fill out firmware slot information for the requested slot. The firmware + * slot information is gathered by requesting the Firmware Slot Information log + * page. The format of the page is described in section 5.10.1.3. + * + * We lazily cache the log page on the first call and then invalidate the cache + * data after a successful firmware download or firmware commit command. + * The cached data is protected by a mutex as the state can change + * asynchronous to this callback. + */ +static int +nvme_ufm_fill_slot(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno, + uint_t slotno, ddi_ufm_slot_t *slot) +{ + nvme_t *nvme = arg; + void *log = NULL; + size_t bufsize; + ddi_ufm_attr_t attr = 0; + char fw_ver[NVME_FWVER_SZ + 1]; + int ret; + + if (imgno > 0 || slotno > (nvme->n_idctl->id_frmw.fw_nslot - 1)) + return (EINVAL); + + mutex_enter(&nvme->n_fwslot_mutex); + if (nvme->n_fwslot == NULL) { + ret = nvme_get_logpage(nvme, B_TRUE, &log, &bufsize, + NVME_LOGPAGE_FWSLOT, 0); + if (ret != DDI_SUCCESS || + bufsize != sizeof (nvme_fwslot_log_t)) { + if (log != NULL) + kmem_free(log, bufsize); + mutex_exit(&nvme->n_fwslot_mutex); + return (EIO); + } + nvme->n_fwslot = (nvme_fwslot_log_t *)log; + } + + /* + * NVMe numbers firmware slots starting at 1 + */ + if (slotno == (nvme->n_fwslot->fw_afi - 1)) + attr |= DDI_UFM_ATTR_ACTIVE; + + if (slotno != 0 || nvme->n_idctl->id_frmw.fw_readonly == 0) + attr |= DDI_UFM_ATTR_WRITEABLE; + + if (nvme->n_fwslot->fw_frs[slotno][0] == '\0') { + attr |= DDI_UFM_ATTR_EMPTY; + } else { + (void) strncpy(fw_ver, nvme->n_fwslot->fw_frs[slotno], + NVME_FWVER_SZ); + fw_ver[NVME_FWVER_SZ] = '\0'; + ddi_ufm_slot_set_version(slot, fw_ver); + } + mutex_exit(&nvme->n_fwslot_mutex); + + ddi_ufm_slot_set_attrs(slot, attr); + + return (0); +} + +static int +nvme_ufm_getcaps(ddi_ufm_handle_t *ufmh, void *arg, ddi_ufm_cap_t *caps) +{ + *caps = DDI_UFM_CAP_REPORT; + return (0); +} diff --git a/usr/src/uts/common/io/nvme/nvme_var.h b/usr/src/uts/common/io/nvme/nvme_var.h index 6f3b53d3ec..110f639845 100644 --- a/usr/src/uts/common/io/nvme/nvme_var.h +++ b/usr/src/uts/common/io/nvme/nvme_var.h @@ -12,7 +12,7 @@ /* * Copyright 2018 Nexenta Systems, Inc. * Copyright 2016 The MathWorks, Inc. All rights reserved. - * Copyright 2017 Joyent, Inc. + * Copyright 2019 Joyent, Inc. * Copyright 2019 Western Digital Corporation. */ @@ -38,6 +38,7 @@ extern "C" { #define NVME_ADMIN_QUEUE 0x4 #define NVME_CTRL_LIMITS 0x8 #define NVME_INTERRUPTS 0x10 +#define NVME_UFM_INIT 0x20 #define NVME_MIN_ADMIN_QUEUE_LEN 16 #define NVME_MIN_IO_QUEUE_LEN 16 @@ -242,6 +243,12 @@ struct nvme { uint32_t n_vendor_event; uint32_t n_unknown_event; + /* DDI UFM handle */ + ddi_ufm_handle_t *n_ufmh; + /* Cached Firmware Slot Information log page */ + nvme_fwslot_log_t *n_fwslot; + /* Lock protecting the cached firmware slot info */ + kmutex_t n_fwslot_mutex; }; struct nvme_namespace { diff --git a/usr/src/uts/common/os/ddi_ufm.c b/usr/src/uts/common/os/ddi_ufm.c index c115bc4df5..ffb04eddec 100644 --- a/usr/src/uts/common/os/ddi_ufm.c +++ b/usr/src/uts/common/os/ddi_ufm.c @@ -184,7 +184,10 @@ ufm_cache_fill(ddi_ufm_handle_t *ufmh) if (ret != 0) goto cache_fail; - ASSERT(img->ufmi_desc != NULL && img->ufmi_nslots != 0); + if (img->ufmi_desc == NULL || img->ufmi_nslots == 0) { + ret = EIO; + goto cache_fail; + } img->ufmi_slots = kmem_zalloc((sizeof (ddi_ufm_slot_t) * img->ufmi_nslots), diff --git a/usr/src/uts/common/sys/nvme.h b/usr/src/uts/common/sys/nvme.h index 0c090da173..f405ea005e 100644 --- a/usr/src/uts/common/sys/nvme.h +++ b/usr/src/uts/common/sys/nvme.h @@ -436,13 +436,22 @@ typedef struct { uint8_t hl_rsvd2[512 - 192]; } nvme_health_log_t; +/* + * The NVMe spec allows for up to seven firmware slots. + */ +#define NVME_MAX_FWSLOTS 7 +#define NVME_FWVER_SZ 8 + typedef struct { - uint8_t fw_afi:3; /* Active Firmware Slot */ + /* Active Firmware Slot */ + uint8_t fw_afi:3; uint8_t fw_rsvd1:1; - uint8_t fw_next:3; /* Next Active Firmware Slot */ + /* Next Active Firmware Slot */ + uint8_t fw_next:3; uint8_t fw_rsvd2:1; uint8_t fw_rsvd3[7]; - char fw_frs[7][8]; /* Firmware Revision / Slot */ + /* Firmware Revision / Slot */ + char fw_frs[NVME_MAX_FWSLOTS][NVME_FWVER_SZ]; uint8_t fw_rsvd4[512 - 64]; } nvme_fwslot_log_t; |