summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Mustacchi <rm@fingolfin.org>2020-05-13 11:04:20 -0700
committerRobert Mustacchi <rm@fingolfin.org>2020-06-12 09:43:33 -0700
commit8d55b80625b903a8ec6c560f6a38b5c16d1f5cfc (patch)
tree352d4300d0758dd8f821b462ebdaa8e106616b0e
parent1fa07ac719189ed3e8a0f8170264877c29bff62b (diff)
downloadillumos-joyent-8d55b80625b903a8ec6c560f6a38b5c16d1f5cfc.tar.gz
12759 Want ability to read ufm images
12758 ufm_detach doesn't properly clean up 12760 igb ufm support 12765 fwflash plugin cleanup handling is buggy Reviewed by: Patrick Mooney <patrick.mooney@joyent.com> Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com> Approved by: Joshua Clulow <josh@sysmgr.org>
-rw-r--r--usr/src/cmd/fwflash/common/fwflash.c21
-rw-r--r--usr/src/cmd/fwflash/plugins/transport/Makefile.targ18
-rw-r--r--usr/src/cmd/fwflash/plugins/transport/common/mapfile-vers-plus4
-rw-r--r--usr/src/cmd/fwflash/plugins/transport/common/ufm.c624
-rw-r--r--usr/src/man/man1m/fwflash.1m90
-rw-r--r--usr/src/man/man7d/ufm.7d176
-rw-r--r--usr/src/man/man9e/ddi_ufm.9e136
-rw-r--r--usr/src/man/man9f/Makefile2
-rw-r--r--usr/src/man/man9f/ddi_ufm.9f2
-rw-r--r--usr/src/man/man9f/ddi_ufm_slot.9f23
-rw-r--r--usr/src/pkg/manifests/system-flash-fwflash.mf1
-rw-r--r--usr/src/pkg/manifests/system-kernel.man9f.inc1
-rw-r--r--usr/src/uts/common/io/igb/igb_main.c171
-rw-r--r--usr/src/uts/common/io/igb/igb_sw.h4
-rw-r--r--usr/src/uts/common/io/ufm.c104
-rw-r--r--usr/src/uts/common/mapfiles/ddi.mapfile1
-rw-r--r--usr/src/uts/common/mapfiles/kernel.mapfile2
-rw-r--r--usr/src/uts/common/os/ddi_ufm.c79
-rw-r--r--usr/src/uts/common/sys/ddi_ufm.h60
-rw-r--r--usr/src/uts/common/sys/ddi_ufm_impl.h4
-rw-r--r--usr/src/uts/intel/igb/Makefile2
21 files changed, 1392 insertions, 133 deletions
diff --git a/usr/src/cmd/fwflash/common/fwflash.c b/usr/src/cmd/fwflash/common/fwflash.c
index 59c3588f24..b08cac921d 100644
--- a/usr/src/cmd/fwflash/common/fwflash.c
+++ b/usr/src/cmd/fwflash/common/fwflash.c
@@ -20,6 +20,7 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2020 Oxide Computer Company
*/
/*
@@ -440,8 +441,7 @@ flash_load_plugins()
continue;
}
- if ((sym = dlsym(tmpplug->handle, "plugin_version"))
- != NULL) {
+ if ((sym = dlsym(tmpplug->handle, "plugin_version")) != NULL) {
if ((*(int *)sym) >= FWPLUGIN_VERSION_2) {
if ((sym = dlsym(tmpplug->handle,
"fw_cleanup")) != NULL) {
@@ -1095,8 +1095,8 @@ static void
fwflash_intr(int sig)
{
- struct devicelist *thisdev;
- struct pluginlist *thisplug;
+ struct devicelist *thisdev, *tmpdev;
+ struct pluginlist *thisplug, *tmpplug;
(void) signal(SIGINT, SIG_IGN);
(void) signal(SIGTERM, SIG_IGN);
@@ -1120,7 +1120,8 @@ fwflash_intr(int sig)
* call the plugin closure routines
*/
if (fw_devices != NULL) {
- TAILQ_FOREACH(thisdev, fw_devices, nextdev) {
+ TAILQ_FOREACH_SAFE(thisdev, fw_devices, nextdev, tmpdev) {
+ TAILQ_REMOVE(fw_devices, thisdev, nextdev);
if (thisdev->plugin->fw_cleanup != NULL) {
/*
* If we've got a cleanup routine, it
@@ -1137,14 +1138,15 @@ fwflash_intr(int sig)
/* We don't free address[] for old plugins */
thisdev->ident = NULL;
thisdev->plugin = NULL;
+ free(thisdev);
}
- /* CONSTCOND */
- TAILQ_REMOVE(fw_devices, thisdev, nextdev);
}
}
if (fw_pluginlist != NULL) {
- TAILQ_FOREACH(thisplug, fw_pluginlist, nextplugin) {
+ TAILQ_FOREACH_SAFE(thisplug, fw_pluginlist, nextplugin,
+ tmpplug) {
+ TAILQ_REMOVE(fw_pluginlist, thisplug, nextplugin);
free(thisplug->filename);
free(thisplug->drvname);
free(thisplug->plugin->filename);
@@ -1162,8 +1164,7 @@ fwflash_intr(int sig)
thisplug->plugin->handle = NULL;
free(thisplug->plugin);
thisplug->plugin = NULL;
- /* CONSTCOND */
- TAILQ_REMOVE(fw_pluginlist, thisplug, nextplugin);
+ free(thisplug);
}
}
diff --git a/usr/src/cmd/fwflash/plugins/transport/Makefile.targ b/usr/src/cmd/fwflash/plugins/transport/Makefile.targ
index 46785e8c6c..c3fefe1a06 100644
--- a/usr/src/cmd/fwflash/plugins/transport/Makefile.targ
+++ b/usr/src/cmd/fwflash/plugins/transport/Makefile.targ
@@ -22,6 +22,7 @@
# Use is subject to license terms.
#
# Copyright 2020 Joyent, Inc.
+# Copyright 2020 Oxide Computer Company
#
include $(SRC)/lib/Makefile.lib
@@ -29,8 +30,9 @@ SES_LIB= ses.so
TAVOR_LIB= tavor.so
HERMON_LIB= hermon.so
SD_LIB= sd.so
+UFM_LIB= ufm.so
-PLUGINS= $(SES_LIB) $(TAVOR_LIB) $(HERMON_LIB) $(SD_LIB)
+PLUGINS= $(SES_LIB) $(TAVOR_LIB) $(HERMON_LIB) $(SD_LIB) $(UFM_LIB)
OBJECTS= $(PLUGINS:%.so=%.o)
DYNLIB= $(PLUGINS:%=%)
@@ -43,10 +45,10 @@ SRCDIR= ../common
include $(SRC)/cmd/fwflash/Makefile.com
-CLEANFILES= $(PLUGINS) $(POFILES) $(POFILE) $(LINTFILE) $(SLINKS)
+CLEANFILES= $(PLUGINS) $(POFILES) $(POFILE) $(LINTFILE) $(SLINKS)
LIBS= $(DYNLIB)
-CFLAGS += $(C_PICFLAGS)
+CFLAGS += $(C_PICFLAGS)
ROOTLIBDIR= $(ROOTUSRLIBFWFLASHIDF)
LDLIBS += -ldevinfo
MAPFILES= ../common/mapfile-vers
@@ -56,22 +58,26 @@ $(SES_LIB):= PICS= pics/$(SES_LIB:%.so=%.o)
$(TAVOR_LIB):= PICS= pics/$(TAVOR_LIB:%.so=%.o)
$(HERMON_LIB):= PICS= pics/$(HERMON_LIB:%.so=%.o)
$(SD_LIB):= PICS= pics/$(SD_LIB:%.so=%.o)
+$(UFM_LIB):= PICS= pics/$(UFM_LIB:%.so=%.o)
$(SES_LIB):= SONAME = $(SES_LIB)
$(TAVOR_LIB):= SONAME = $(TAVOR_LIB)
$(HERMON_LIB):= SONAME = $(HERMON_LIB)
$(SD_LIB):= SONAME = $(SD_LIB)
+$(UFM_LIB):= SONAME = $(UFM_LIB)
$(HERMON_LIB):= MAPFILES += ../common/mapfile-vers-hermon
$(SD_LIB):= MAPFILES += ../common/mapfile-vers-plus
+$(UFM_LIB):= MAPFILES += ../common/mapfile-vers-plus
$(HERMON_LIB):= LDLIBS += -lc
$(TAVOR_LIB):= LDLIBS += -lc
$(SES_LIB):= LDLIBS += -L$(ROOT)/usr/lib/scsi -lscsi -lses -lnvpair -lc
$(SD_LIB):= LDLIBS += -L$(ROOT)/usr/lib/scsi -lscsi -lumem -lc
+$(UFM_LIB):= LDLIBS += -lpcidb -lnvpair -lc
-$(SES_LIB):= DYNFLAGS += -R/usr/lib/scsi
-$(SD_LIB):= DYNFLAGS += -R/usr/lib/scsi
+$(SES_LIB):= DYNFLAGS += -R/usr/lib/scsi
+$(SD_LIB):= DYNFLAGS += -R/usr/lib/scsi
.KEEP STATE:
@@ -89,5 +95,3 @@ lint: $(LINTFILE)
_msg msg: $(POFILE)
include $(SRC)/lib/Makefile.targ
-
-
diff --git a/usr/src/cmd/fwflash/plugins/transport/common/mapfile-vers-plus b/usr/src/cmd/fwflash/plugins/transport/common/mapfile-vers-plus
index 5891e2d43d..3a86e54c1f 100644
--- a/usr/src/cmd/fwflash/plugins/transport/common/mapfile-vers-plus
+++ b/usr/src/cmd/fwflash/plugins/transport/common/mapfile-vers-plus
@@ -38,8 +38,8 @@
$mapfile_version 2
-SYMBOL_VERSION SUNWprivate {
+SYMBOL_VERSION SUNWprivate {
global:
fw_cleanup;
+ plugin_version;
};
-
diff --git a/usr/src/cmd/fwflash/plugins/transport/common/ufm.c b/usr/src/cmd/fwflash/plugins/transport/common/ufm.c
new file mode 100644
index 0000000000..ec21b27efd
--- /dev/null
+++ b/usr/src/cmd/fwflash/plugins/transport/common/ufm.c
@@ -0,0 +1,624 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2020 Oxide Computer Company
+ */
+
+/*
+ * fwflash(1M) backend for UFMs.
+ */
+
+#include <libdevinfo.h>
+#include <strings.h>
+#include <libintl.h>
+#include <pcidb.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <libnvpair.h>
+#include <sys/ddi_ufm.h>
+#include <sys/sysmacros.h>
+#include <fwflash/fwflash.h>
+
+/*
+ * We pick a fixed size unit to work on.
+ */
+#define UFM_READ_BUFLEN (16 * 1024 * 1024)
+
+/*
+ * These are indexes into the addresses array that we use.
+ */
+#define UFM_ADDR_PATH 0
+#define UFM_ADDR_SUB 1
+#define UFM_ADDR_CAP 2
+
+typedef struct ufmfw_ident_arg {
+ uint_t uia_nfound;
+ int uia_index;
+ int uia_err;
+} ufmfw_ident_arg_t;
+
+/*
+ * fwflash requires we declare our driver name as data with this name.
+ */
+const char drivername[] = "ufm";
+const int plugin_version = FWPLUGIN_VERSION_2;
+
+/*
+ * External data from fwflash.
+ */
+extern di_node_t rootnode;
+extern struct fw_plugin *self;
+
+/*
+ * Global, shared data.
+ */
+static int ufmfw_ufm_fd = -1;
+static pcidb_hdl_t *ufmfw_pcidb;
+static boolean_t ufmfw_ready = B_FALSE;
+
+/*
+ * Read image zero and slot zero that we find.
+ */
+int
+fw_readfw(struct devicelist *flashdev, const char *filename)
+{
+ nvlist_t **images, **slots;
+ uint_t nimages, nslots, caps;
+ uint64_t imgsize, offset;
+ void *buf;
+ int fd;
+ nvlist_t *nvl = flashdev->ident->encap_ident;
+
+ caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP];
+ if ((caps & DDI_UFM_CAP_READIMG) == 0) {
+ logmsg(MSG_ERROR, "%s: device %s does not support reading "
+ "images\n", flashdev->drvname, flashdev->access_devname);
+ return (FWFLASH_FAILURE);
+ }
+
+ if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images,
+ &nimages) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: %s missing UFM image data\n"),
+ flashdev->drvname, flashdev->access_devname);
+ return (FWFLASH_FAILURE);
+ }
+
+ if (nimages == 0) {
+ logmsg(MSG_ERROR, gettext("%s: %s has no UFM images\n"),
+ flashdev->drvname, flashdev->access_devname);
+ return (FWFLASH_FAILURE);
+ }
+
+ if (nvlist_lookup_nvlist_array(images[0], DDI_UFM_NV_IMAGE_SLOTS,
+ &slots, &nslots) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: image zero of %s has no "
+ "slots\n"), flashdev->drvname, flashdev->access_devname);
+ return (FWFLASH_FAILURE);
+ }
+
+ if (nvlist_lookup_uint64(slots[0], DDI_UFM_NV_SLOT_IMGSIZE,
+ &imgsize) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: device %s doesn't have an image "
+ "size\n"), flashdev->drvname, flashdev->access_devname);
+ return (FWFLASH_FAILURE);
+ }
+
+ logmsg(MSG_INFO, gettext("%s: Need to read %" PRIu64 " bytes\n"),
+ flashdev->drvname, imgsize);
+
+ if ((buf = malloc(UFM_READ_BUFLEN)) == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: Failed to allocate data "
+ "buffer\n"), flashdev->drvname);
+ return (FWFLASH_FAILURE);
+ }
+
+ if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to open file %s: %s\n"),
+ flashdev->drvname, filename, strerror(errno));
+ free(buf);
+ return (FWFLASH_FAILURE);
+ }
+
+ offset = 0;
+ while (imgsize > 0) {
+ ufm_ioc_readimg_t rimg;
+ uint64_t toread = MIN(imgsize, UFM_READ_BUFLEN);
+ size_t woff;
+
+ bzero(&rimg, sizeof (rimg));
+ rimg.ufri_version = DDI_UFM_CURRENT_VERSION;
+ rimg.ufri_imageno = 0;
+ rimg.ufri_slotno = 0;
+ rimg.ufri_offset = offset;
+ rimg.ufri_len = toread;
+ rimg.ufri_buf = buf;
+ (void) strlcpy(rimg.ufri_devpath,
+ flashdev->addresses[UFM_ADDR_PATH],
+ sizeof (rimg.ufri_devpath));
+ logmsg(MSG_INFO, gettext("%s: want to read %" PRIu64 " bytes "
+ "at offset %" PRIu64 "\n"), flashdev->drvname,
+ rimg.ufri_len, rimg.ufri_offset);
+
+ if (ioctl(ufmfw_ufm_fd, UFM_IOC_READIMG, &rimg) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to read image: "
+ "%s\n"), flashdev->drvname, strerror(errno));
+ free(buf);
+ (void) close(fd);
+ return (FWFLASH_FAILURE);
+ }
+
+ logmsg(MSG_INFO, gettext("%s: read %" PRIu64 " bytes at offset "
+ "%" PRIu64 "\n"), flashdev->drvname, rimg.ufri_nread,
+ offset);
+ offset += rimg.ufri_nread;
+ imgsize -= rimg.ufri_nread;
+
+ woff = 0;
+ while (rimg.ufri_nread > 0) {
+ size_t towrite = MIN(rimg.ufri_nread, UFM_READ_BUFLEN);
+ ssize_t ret = write(fd, buf + woff, towrite);
+ if (ret == -1) {
+ logmsg(MSG_ERROR, gettext("%s: failed to write "
+ "to %s: %s\n"), flashdev->drvname, filename,
+ strerror(errno));
+ free(buf);
+ (void) close(fd);
+ return (FWFLASH_FAILURE);
+ }
+
+ rimg.ufri_nread -= ret;
+ woff += ret;
+ }
+ }
+
+ free(buf);
+ if (close(fd) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to finish writing to %s: "
+ "%s\n"), flashdev->drvname, filename, strerror(errno));
+ return (FWFLASH_FAILURE);
+ }
+ logmsg(MSG_INFO, gettext("%s: successfully wrote image to %s\n"),
+ flashdev->drvname, filename);
+ return (FWFLASH_SUCCESS);
+}
+
+
+int
+fw_writefw(struct devicelist *flashdev)
+{
+ return (FWFLASH_SUCCESS);
+}
+
+static void
+ufmfw_flashdev_free(struct devicelist *flashdev)
+{
+ if (flashdev == NULL)
+ return;
+ if (flashdev->ident != NULL) {
+ free(flashdev->ident->vid);
+ free(flashdev->ident->pid);
+ nvlist_free(flashdev->ident->encap_ident);
+ }
+ free(flashdev->ident);
+ free(flashdev->drvname);
+ free(flashdev->classname);
+ free(flashdev->access_devname);
+ di_devfs_path_free(flashdev->addresses[UFM_ADDR_PATH]);
+ free(flashdev->addresses[UFM_ADDR_SUB]);
+ free(flashdev);
+}
+
+/*
+ * Check if a node is a PCI device. This is so we can deal with VPD information.
+ * Hopefully we'll have a generalized devinfo or fmtopo VPD section which we can
+ * then use for this instead.
+ */
+static boolean_t
+ufmfw_node_pci(di_node_t node)
+{
+ while (node != DI_NODE_NIL) {
+ char *strs;
+ int ret = di_prop_lookup_strings(DDI_DEV_T_ANY, node,
+ "device_type", &strs);
+
+ if (ret > 0) {
+ if (strcmp(strs, "pci") == 0 ||
+ strcmp(strs, "pciex") == 0) {
+ return (B_TRUE);
+ }
+ }
+
+ node = di_parent_node(node);
+ }
+ return (B_FALSE);
+}
+
+/*
+ * Cons up VPD information based on the PCI ID. Hopefully in time we'll use the
+ * actual PCI VPD information and more generally allow a device to specify its
+ * vpd automatically.
+ */
+static boolean_t
+ufmfw_fill_vpd(struct devicelist *flashdev, di_node_t node)
+{
+ int *vid, *did, *svid, *sdid;
+ pcidb_vendor_t *vend = NULL;
+ pcidb_device_t *dev = NULL;
+ pcidb_subvd_t *subdv = NULL;
+ char *vstr, *dstr;
+
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid) != 1) {
+ logmsg(MSG_ERROR, gettext("%s: %s missing 'vendor-id' "
+ "property\n"), flashdev->drvname, flashdev->access_devname);
+ return (B_FALSE);
+ }
+
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did) != 1) {
+ logmsg(MSG_ERROR, gettext("%s: %s missing 'device-id' "
+ "property\n"), flashdev->drvname, flashdev->access_devname);
+ return (B_FALSE);
+ }
+
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-vendor-id",
+ &svid) != 1 || di_prop_lookup_ints(DDI_DEV_T_ANY, node,
+ "subsystem-device-id", &sdid) != 1) {
+ svid = NULL;
+ sdid = NULL;
+ }
+
+ vend = pcidb_lookup_vendor(ufmfw_pcidb, vid[0]);
+ if (vend != NULL) {
+ dev = pcidb_lookup_device_by_vendor(vend, did[0]);
+ }
+
+ if (dev != NULL && svid != NULL && sdid != NULL) {
+ subdv = pcidb_lookup_subvd_by_device(dev, svid[0], sdid[0]);
+ }
+
+ if (vend != NULL) {
+ vstr = strdup(pcidb_vendor_name(vend));
+ } else {
+ (void) asprintf(&vstr, "pci:%x", vid[0]);
+ }
+
+ if (vstr == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to allocate vid "
+ "string\n"), flashdev->drvname);
+ return (B_FALSE);
+ }
+ flashdev->ident->vid = vstr;
+
+ if (dstr != NULL) {
+ dstr = strdup(pcidb_device_name(dev));
+ } else {
+ (void) asprintf(&dstr, "pci:%x", did[0]);
+ }
+
+ if (dstr == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to allocate pid "
+ "string\n"), flashdev->drvname);
+ return (B_FALSE);
+ }
+ flashdev->ident->pid = dstr;
+
+ if (subdv != NULL) {
+ /*
+ * Because this is optional, don't fail if we fail to duplicate
+ * this.
+ */
+ flashdev->addresses[UFM_ADDR_SUB] =
+ strdup(pcidb_subvd_name(subdv));
+ if (flashdev->addresses[UFM_ADDR_SUB] == NULL) {
+ logmsg(MSG_WARN, gettext("%s: failed to allocate vpd "
+ "subsystem name\n"), flashdev->drvname);
+ }
+ }
+
+ return (B_TRUE);
+}
+
+static int
+ufmfw_di_walk_cb(di_node_t node, void *arg)
+{
+ int ret;
+ boolean_t found = B_FALSE;
+ di_prop_t prop = DI_PROP_NIL;
+ ufmfw_ident_arg_t *uia = arg;
+ struct devicelist *flashdev = NULL;
+ ufm_ioc_getcaps_t caps;
+ ufm_ioc_bufsz_t bufsz;
+ ufm_ioc_report_t rep;
+ char *devfs, *packnvl;
+ nvlist_t *nvl = NULL;
+
+ while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) {
+ const char *pname = di_prop_name(prop);
+ if (strcmp(pname, "ddi-ufm-capable") == 0) {
+ found = B_TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ return (DI_WALK_CONTINUE);
+ }
+
+ if (!ufmfw_node_pci(node)) {
+ return (DI_WALK_CONTINUE);
+ }
+
+ if ((devfs = di_devfs_path(node)) == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to get device node "
+ "path\n"), drivername);
+ goto err;
+ }
+
+ bzero(&caps, sizeof (caps));
+ caps.ufmg_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(caps.ufmg_devpath, devfs, sizeof (caps.ufmg_devpath));
+ if (ioctl(ufmfw_ufm_fd, UFM_IOC_GETCAPS, &caps) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to get UFM caps for "
+ "UFM compatible device %s: %s\n"), drivername, devfs,
+ strerror(errno));
+ di_devfs_path_free(devfs);
+ return (DI_WALK_CONTINUE);
+ }
+
+ /*
+ * If nothing is supported just leave it be.
+ */
+ if (caps.ufmg_caps == 0) {
+ di_devfs_path_free(devfs);
+ return (DI_WALK_CONTINUE);
+ }
+
+ bzero(&bufsz, sizeof (bufsz));
+ bufsz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(bufsz.ufbz_devpath, devfs, sizeof (bufsz.ufbz_devpath));
+ if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORTSZ, &bufsz) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to get UFM report size "
+ "for device %s: %s\n"), drivername, devfs,
+ strerror(errno));
+ di_devfs_path_free(devfs);
+ return (DI_WALK_CONTINUE);
+ }
+
+ if ((packnvl = malloc(bufsz.ufbz_size)) == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to allocate %zu bytes "
+ "for report buffer\n"), drivername, bufsz.ufbz_size);
+ di_devfs_path_free(devfs);
+ goto err;
+ }
+ bzero(&rep, sizeof (rep));
+ rep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ rep.ufmr_bufsz = bufsz.ufbz_size;
+ rep.ufmr_buf = packnvl;
+ (void) strlcpy(rep.ufmr_devpath, devfs, sizeof (rep.ufmr_devpath));
+ if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORT, &rep) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to get UFM report "
+ "for device %s: %s\n"), drivername, devfs,
+ strerror(errno));
+ free(packnvl);
+ di_devfs_path_free(devfs);
+ return (DI_WALK_CONTINUE);
+ }
+
+ if ((ret = nvlist_unpack(packnvl, rep.ufmr_bufsz, &nvl, 0)) != 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to unpack UFM report "
+ "for device %s: %s\n"), drivername, devfs, strerror(ret));
+ free(packnvl);
+ di_devfs_path_free(devfs);
+ return (DI_WALK_CONTINUE);
+
+ }
+ free(packnvl);
+
+ if ((flashdev = calloc(1, sizeof (*flashdev))) == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to allocate new "
+ "device entry for node %s\n"), drivername, devfs);
+ di_devfs_path_free(devfs);
+ goto err;
+ }
+
+ flashdev->addresses[UFM_ADDR_PATH] = devfs;
+
+ if (asprintf(&flashdev->access_devname, "/devices%s",
+ flashdev->addresses[UFM_ADDR_PATH]) == -1) {
+ logmsg(MSG_ERROR, gettext("%s: failed to construct device "
+ "path\n"), drivername);
+ goto err;
+ }
+ if ((flashdev->drvname = strdup(drivername)) == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to construct driver "
+ "name\n"), drivername);
+ goto err;
+ }
+ if ((flashdev->classname = strdup(drivername)) == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to allocate vpd "
+ "data\n"), drivername);
+ goto err;
+ }
+
+ if ((flashdev->ident = calloc(1, sizeof (struct vpr))) == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to construct class "
+ "name\n"), drivername);
+ goto err;
+ }
+ if (!ufmfw_fill_vpd(flashdev, node)) {
+ goto err;
+ }
+
+ flashdev->ident->encap_ident = nvl;
+
+ flashdev->index = uia->uia_index;
+ uia->uia_index++;
+ flashdev->addresses[UFM_ADDR_CAP] = (void *)(uintptr_t)caps.ufmg_caps;
+ flashdev->plugin = self;
+ uia->uia_nfound++;
+
+ TAILQ_INSERT_TAIL(fw_devices, flashdev, nextdev);
+
+ return (DI_WALK_CONTINUE);
+
+err:
+ nvlist_free(nvl);
+ uia->uia_err = FWFLASH_FAILURE;
+ ufmfw_flashdev_free(flashdev);
+ return (DI_WALK_TERMINATE);
+}
+
+int
+fw_identify(int start)
+{
+ ufmfw_ident_arg_t uia;
+
+ if (!ufmfw_ready) {
+ return (FWFLASH_FAILURE);
+ }
+
+ uia.uia_nfound = 0;
+ uia.uia_index = start;
+ uia.uia_err = FWFLASH_SUCCESS;
+ (void) di_walk_node(rootnode, DI_WALK_CLDFIRST, &uia,
+ ufmfw_di_walk_cb);
+ if (uia.uia_nfound == 0) {
+ return (FWFLASH_FAILURE);
+ }
+
+ return (uia.uia_err);
+}
+
+int
+fw_devinfo(struct devicelist *flashdev)
+{
+ nvlist_t *nvl, **images;
+ uint_t nimages, img, caps;
+
+ (void) printf(gettext("Device[%d] %s\n"), flashdev->index,
+ flashdev->access_devname);
+ (void) printf(gettext("Class [%s]\n"), flashdev->classname);
+ (void) printf(gettext("\tVendor: %s\n\tDevice: %s\n"),
+ flashdev->ident->vid, flashdev->ident->pid);
+ if (flashdev->addresses[UFM_ADDR_SUB] != NULL) {
+ (void) printf(gettext("\tSubsystem: %s\n"),
+ flashdev->addresses[UFM_ADDR_SUB]);
+ }
+
+ caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP];
+ if (caps != 0) {
+ boolean_t first = B_TRUE;
+ (void) printf(gettext("\tCapabilities: "));
+ if (caps & DDI_UFM_CAP_REPORT) {
+ (void) printf(gettext("Report"));
+ first = B_FALSE;
+ }
+
+ if (caps & DDI_UFM_CAP_READIMG) {
+ (void) printf(gettext("%sRead Image"),
+ first ? "" : ", ");
+ }
+ (void) printf("\n");
+ }
+
+ nvl = flashdev->ident->encap_ident;
+ if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images,
+ &nimages) != 0) {
+ goto done;
+ }
+
+ for (img = 0; img < nimages; img++) {
+ nvlist_t **slots;
+ uint_t nslots, s;
+ char *desc;
+
+ if (nvlist_lookup_nvlist_array(images[img],
+ DDI_UFM_NV_IMAGE_SLOTS, &slots, &nslots) != 0) {
+ goto done;
+ }
+
+ if (nvlist_lookup_string(images[img], DDI_UFM_NV_IMAGE_DESC,
+ &desc) != 0) {
+ desc = NULL;
+ }
+
+ if (desc != NULL) {
+ (void) printf(gettext("\tImage %d: %s\n"), img, desc);
+ } else {
+ (void) printf(gettext("\tImage %d:\n"), img);
+ }
+
+ for (s = 0; s < nslots; s++) {
+ uint32_t attr;
+ char *version;
+
+ if (nvlist_lookup_uint32(slots[s], DDI_UFM_NV_SLOT_ATTR,
+ &attr) != 0) {
+ attr = 0;
+ }
+
+ if (nvlist_lookup_string(slots[s],
+ DDI_UFM_NV_SLOT_VERSION, &version) != 0) {
+ version = "<unknown>";
+ }
+
+ printf(gettext("\t Slot %d (%c|%c|%c): %s\n"), s,
+ attr & DDI_UFM_ATTR_READABLE ? 'r' : '-',
+ attr & DDI_UFM_ATTR_WRITEABLE ? 'w' : '-',
+ attr & DDI_UFM_ATTR_ACTIVE ? 'a' : '-',
+ attr & DDI_UFM_ATTR_EMPTY ? "<empty>" : version);
+
+ }
+ }
+
+done:
+ (void) printf("\n\n");
+ return (FWFLASH_SUCCESS);
+}
+
+void
+fw_cleanup(struct devicelist *flashdev)
+{
+ ufmfw_flashdev_free(flashdev);
+}
+
+#pragma init(ufmfw_init)
+static void
+ufmfw_init(void)
+{
+ ufmfw_ufm_fd = open("/dev/ufm", O_RDONLY);
+ if (ufmfw_ufm_fd < 0) {
+ logmsg(MSG_ERROR, gettext("%s: failed to open /dev/ufm: %s\n"),
+ drivername, strerror(errno));
+ return;
+ }
+
+ ufmfw_pcidb = pcidb_open(PCIDB_VERSION);
+ if (ufmfw_pcidb == NULL) {
+ logmsg(MSG_ERROR, gettext("%s: failed to open libpcidb: %s\n"),
+ drivername, strerror(errno));
+ return;
+ }
+ ufmfw_ready = B_TRUE;
+}
+
+#pragma fini(ufmfw_fini)
+static void
+ufmfw_fini(void)
+{
+ pcidb_close(ufmfw_pcidb);
+ if (ufmfw_ufm_fd >= 0) {
+ (void) close(ufmfw_ufm_fd);
+ }
+ ufmfw_ready = B_FALSE;
+}
diff --git a/usr/src/man/man1m/fwflash.1m b/usr/src/man/man1m/fwflash.1m
index 3eac6f8689..d81d7f7dae 100644
--- a/usr/src/man/man1m/fwflash.1m
+++ b/usr/src/man/man1m/fwflash.1m
@@ -1,13 +1,13 @@
'\" te
.\" Copyright (c) 2008, Sun Microsystems, Inc. All Rights Reserved
+.\" Copyright 2020 Oxide Computer Company
.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License.
.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. See the License for the specific language governing permissions and limitations under the License.
.\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH FWFLASH 1M "Nov 4, 2008"
+.TH FWFLASH 1M "June 9, 2020"
.SH NAME
fwflash \- firmware query and update utility
.SH SYNOPSIS
-.LP
.nf
\fB/usr/sbin/fwflash\fR [\fB-l\fR [\fB-c\fR \fIdevice_class\fR | \fBALL\fR ]]
| [\fB-v\fR] | [\fB-h\fR]
@@ -20,8 +20,6 @@ fwflash \- firmware query and update utility
.fi
.SH DESCRIPTION
-.sp
-.LP
The \fBfwflash\fR command writes a binary image file to supported flashable
devices attached to a Solaris host. It also provides the ability to read
firmware to a file if supported by the device. Because changing the firmware in
@@ -38,8 +36,6 @@ display only specified classes of devices. The second form of the command
provides the operations to read or write the firmware images to specific
devices.
.SH OPTIONS
-.sp
-.LP
The following options are supported:
.sp
.ne 2
@@ -50,7 +46,7 @@ The following options are supported:
.RS 4n
An optional parameter, valid only when used with the \fB-l\fR option. This
option causes the command to list only devices of a specific class type.
-Currently supported classes are \fBIB\fR, \fBses\fR, \fBsesgen\fR, or
+Currently supported classes are \fBIB\fR, \fBses\fR, \fBsd\fR, \fBufm\fR, or
\fBALL\fR. If \fB-c\fR is not specified for the \fB-l\fRoption, the class
defaults to \fBALL\fR.
.RE
@@ -116,6 +112,12 @@ two of them can be set with the same value.
.sp
For SCSI Enclosure Services (\fBses\fR or \fBsgen\fR) devices, an identifying
target-port worldwide name is displayed, if available.
+.sp
+For \fBufm\fR(7D) based devices, identifying information such as PCI
+identifiers and supported capabilities such as the ability to read
+firmware images or slot information is reported. For each firmware image
+on the device, each slot is displayed along with information about the
+slot's version and attributes.
.RE
.sp
@@ -129,8 +131,7 @@ Specify the path to a file to create when reading the firmware from the device.
The \fB-f\fR and \fB-r\fR options are mutually exclusive.
.sp
Not all flashable devices support reading firmware images back from the device.
-At present, only InfiniBand (IB) devices are supported for this operation. A
-message will be displayed if the selected device does not support this
+A message will be displayed if the selected device does not support this
operation.
.RE
@@ -158,7 +159,6 @@ that allows you to forcibly flash an incompatible firmware image onto a device.
.RE
.SH EXAMPLES
-.LP
\fBExample 1 \fREntering Command Without Arguments
.sp
.LP
@@ -170,7 +170,6 @@ arguments.
.nf
example# \fBfwflash\fR
Usage:
-Usage:
fwflash [-l [-c device_class | ALL]] | [-v] | [-h]
fwflash [-f file1,file2,file3,... | -r file] [-y] -d device_path
@@ -232,8 +231,8 @@ Device[1], /devices/pci@0,0/pci8086,3597@4/pci15b3,6278@0:devctl
.sp
.LP
-Alternatively, for a SAS Expander presented as a SCSI Enclosure Services device
-, we might see output such as this:
+Alternatively, for a SAS Expander presented as a SCSI Enclosure Services device,
+we might see output such as this:
.sp
.in +2
@@ -250,6 +249,37 @@ Device[0] /devices/pci@0/pci@0/pci@2/scsi@0/ses@3,0:ses
.in -2
.sp
+.sp
+.LP
+Finally, for devices that support the system's upgradeable firmware
+module APIs (see \fBufm\fR(7D)), one might see output like:
+
+.sp
+.in +2
+.nf
+example# \fBfwflash -l\fR
+List of available devices:
+Device[0] /devices/pci@0,0/pci1022,1483@3,2/pci8086,390d@0
+Class [ufm]
+ Vendor: Intel Corporation
+ Device: SSD 660P Series
+ Capabilities: Report
+ Image 0: Firmware
+ Slot 0 (-|w|a): 002C
+ Slot 1 (-|w|-): 002C
+
+
+Device[1] /devices/pci@3d,0/pci1022,1483@3,1/pci1849,1521@0
+Class [ufm]
+ Vendor: Intel Corporation
+ Device: I350 Gigabit Network Connection
+ Capabilities: Report, Read Image
+ Image 0: NVM
+ Slot 0 (r|w|a): 1.69
+.fi
+.in -2
+.sp
+
.LP
\fBExample 3 \fRFlash Upgrading an IB HCA Device
.sp
@@ -304,11 +334,11 @@ the \fB-y\fR option so that read occurs without prompting.
.sp
.in +2
.nf
-example# \fBfwflash -y -r /firmware.bin \e
+example# \fBfwflash -y -r ./firmware.bin \e
-d /devices/pci@1d,700000/pci@1/pci15b3,5a44@0:devctl\fR
About to read firmware on:
/devices/pci@1d,700000/pci@1/pci15b3,5a44@0:devctl
-to filename: /firmware.bin
+to filename: ./firmware.bin
Reading . . .
Done.
@@ -339,12 +369,9 @@ fwflash: No flashable devices in this system
.LP
Each plugin found in \fB/usr/lib/fwflash/identify\fR is loaded in turn, and
walks the system device tree, determining whether any currently-attached
-devices can be flashed. For the list of device types and drivers that are
-currently supported, please see the \fBNOTES\fR section below.
+devices can be flashed.
.SH RETURN VALUES
-.sp
-.LP
The \fBfwflash\fR command returns the following values:
.sp
.ne 2
@@ -367,8 +394,6 @@ Failure
.RE
.SH ATTRIBUTES
-.sp
-.LP
See \fBattributes\fR(5) for descriptions of the following attributes:
.sp
@@ -383,9 +408,8 @@ Interface Stability Committed
.TE
.SH SEE ALSO
-.sp
-.LP
-\fBattributes\fR(5), \fBhermon\fR(7D), \fBses\fR(7D), \fBtavor\fR(7D)
+\fBattributes\fR(5), \fBhermon\fR(7D), \fBses\fR(7D), \fBtavor\fR(7D),
+\fBufm\fR(7D)
.sp
.LP
The InfiniBand Trade Association website, http://www.infinibandta.org
@@ -401,21 +425,3 @@ The SCSI Storage Interfaces committee website, http://www.t10.org
.sp
.LP
\fISerial Attached SCSI-2, SAS2\fR
-.SH NOTES
-.sp
-.LP
-The \fBfwflash\fR command supports:
-.RS +4
-.TP
-.ie t \(bu
-.el o
-InfiniBand Host Channel Adapters (IB HCAs) containing either the AMD or the
-Intel parallel flash parts.
-.RE
-.RS +4
-.TP
-.ie t \(bu
-.el o
-SCSI Enclosure Services devices such as SAS Expanders, attached with
-\fBses\fR(7D) drivers.
-.RE
diff --git a/usr/src/man/man7d/ufm.7d b/usr/src/man/man7d/ufm.7d
index 5cd2b3581e..60fdfa9983 100644
--- a/usr/src/man/man7d/ufm.7d
+++ b/usr/src/man/man7d/ufm.7d
@@ -10,8 +10,9 @@
.\"
.\"
.\" Copyright 2019 Joyent, Inc.
+.\" Copyright 2020 Oxide Computer Company
.\"
-.Dd Jun 5, 2019
+.Dd June 9, 2020
.Dt UFM 7D
.Os
.Sh NAME
@@ -24,10 +25,15 @@
.Sh DESCRIPTION
The
.Nm
-device is a character special file that provides acccess to
+device is a character special file that provides access to
Upgradeable Firmware Image information, as described in
.Xr ddi_ufm 9E
via a private ioctl interface.
+.Pp
+The UFM interfaces described below are used in the implementation of the
+system through tools such as
+.Xr fwflash 1M
+or as part of the fault management architecture.
.Sh FILES
.Bl -tag -width Pa
.It Pa /kernel/drv/amd64/ufm
@@ -47,56 +53,91 @@ The
.Dv UFM_IOC_GETCAPS
ioctl is used to retrieve the set of DDI UFM capabilities supported by this
device instance.
-Currently there is only a single capability
-.Dv DDI_UFM_CAP_REPORT ,
-which indicates
-that the driver is capable of reporting UFM information.
.Pp
The ddi_ufm_cap_t type defines a bitfield enumerating the full set of DDI UFM
capabilities.
.Bd -literal
typedef enum {
DDI_UFM_CAP_REPORT = 1 << 0,
+ DDI_UFM_CAP_READIMG = 1 << 1
} ddi_ufm_cap_t;
.Ed
.Pp
-The ufm_ioc_getcaps_t type defines the input/output data for the
+The capabilities mean:
+.Bl -tag -width Dv
+.It Dv DDI_UFM_CAP_REPORT
+Indicates that the device is capable of reporting UFM information and
+supports the
+.Dv UFM_IOC_REPORT
+and
+.Dv UFM_IOC_REPORTSZ
+ioctls.
+.It Dv DDI_UFM_CAP_READIMG
+Indicates that the device is capable of retrieving a firmware image from
+a slot and transferring it back to the caller.
+The
+.Dv UFM_IOC_READIMG
+ioctl is supported.
+.El
+.Pp
+The
+.Vt ufm_ioc_getcaps_t
+structure defines the input/output data for the
.Dv UFM_IOC_GETCAPS
ioctl.
-Callers should specify the ufmg_version and ufmg_devpath fields.
-On success the ufmg_caps field will be filled in with a value indicating the
-supported UFM capabilities of the device specified in ufmg_devpath.
+Callers should specify the
+.Fa ufmg_version
+and
+.Fa ufmg_devpath
+fields.
+On success the
+.Fa ufmg_caps
+field will be filled in with a value indicating the
+supported UFM capabilities of the device specified in
+.Fa ufmg_devpath .
.Bd -literal
typedef struct ufm_ioc_getcaps {
- uint_t ufmg_version; /* DDI_UFM_VERSION */
+ uint_t ufmg_version; /* DDI_UFM_VERSION_ONE */
uint_t ufmg_caps; /* UFM Caps */
char ufmg_devpath[MAXPATHLEN];
} ufm_ioc_getcaps_t;
.Ed
-.It UFM_IOC_REPORTSZ
+.It Dv UFM_IOC_REPORTSZ
The
-.Dv UFM_IOC_REPORTSZ ioctl is used to retrieve the amount of space
+.Dv UFM_IOC_REPORTSZ
+ioctl is used to retrieve the amount of space
(in bytes) required to hold the UFM data for this device instance.
This should be used to allocate a sufficiently sized buffer for the
.Dv UFM_IOC_REPORT
ioctl.
.Pp
-The ufm_ioc_bufsz_t struct defines the input/output data for the
-.Dv UFM_IOC_REPORTSZ ioctl.
-Callers should specify the ufbz_version and ufbz_devpath fields.
-On success the ufmg_size field will be filled in with the required buffer size.
+The
+.Vt ufm_ioc_bufsz_t
+structure defines the input/output data for the
+.Dv UFM_IOC_REPORTSZ
+ioctl.
+Callers should specify the
+.Fa ufbz_version
+and
+.Fa ufbz_devpath
+fields.
+On success the
+.Fa ufbz_size
+field will be filled in with the required buffer size.
.Bd -literal
typedef struct ufm_ioc_bufsz {
- uint_t ufbz_version; /* DDI_UFM_VERSION */
+ uint_t ufbz_version; /* DDI_UFM_VERSION_ONE */
size_t ufbz_size; /* sz of buf to be returned by ioctl */
char ufbz_devpath[MAXPATHLEN];
} ufm_ioc_bufsz_t;
.Ed
-.It UFM_IOC_REPORT
+.It Dv UFM_IOC_REPORT
The
-.Dv UFM_IOC_REPORT ioctl returns UFM image and slot data in the form of a
-packed nvlist.
-The ufm_ioc_report_t struct defines the input/output data for the
+.Dv UFM_IOC_REPORT
+ioctl returns UFM image and slot data in the form of a packed nvlist.
+The
+.Vt ufm_ioc_report_t
+structure defines the input/output data for the
.Dv UFM_IOC_REPORT
ioctl.
Callers should specify the ufmr_version, ufmr_bufsz and ufmr_devpath fields.
@@ -106,31 +147,93 @@ This data can be unpacked and decoded into an nvlist using
.Xr nvlist_unpack 3NVPAIR .
.Bd -literal
typedef struct ufm_ioc_report {
- uint_t ufmr_version; /* DDI_UFM_VERSION */
+ uint_t ufmr_version; /* DDI_UFM_VERSIONONE */
size_t ufmr_bufsz; /* size of caller-supplied buffer */
caddr_t ufmr_buf; /* buf to hold packed output nvl */
char ufmr_devpath[MAXPATHLEN];
} ufm_ioc_report_t;
+.Ed
.Pp
Due to the asynchronous nature of the system, it's possible for a device to
undergo a configuration change in between a
-.Dv UFM_IOC_REPORTSZ ioctl and a subsequent
-.Dv UFM_IOC_REPORT ioctl that would alter the size of the buffer
+.Dv UFM_IOC_REPORTSZ
+ioctl and a subsequent
+.Dv UFM_IOC_REPORT
+ioctl that would alter the size of the buffer
required to hold the UFM data.
.Pp
If the size of buffer supplied in the
-.Dv UFM_IOC_REPORT ioctl is greater than is required to hold the UFM data, then
+.Dv UFM_IOC_REPORT
+ioctl is greater than is required to hold the UFM data, then
the ioctl will succeed and the ufmr_bufsz field will be updated to reflect the
actual size of the returned UFM data.
If the size of buffer supplied in the
-.Dv UFM_IOC_REPORT ioctl is less than what is required to hold the UFM data,
+.Dv UFM_IOC_REPORT
+ioctl is less than what is required to hold the UFM data,
the ioctl will fail with errno set to
.Er EOVERFLOW .
+.It Dv UFM_IOC_READIMG
+The
+.Dv UFM_IOC_READIMG
+ioctl retrieves a firmware image and slot from a device.
+The
+.Vt ufm_ioc_readimg_t
+structure defines the input and output data for the ioctl.
+Devices may have their own alignment and size constraints which may be
+enforced upon issuing this ioctl.
+The structure has the following form:
+.Bd -literal
+typedef struct ufm_ioc_readimg {
+ uint_t ufri_version;
+ uint_t ufri_imageno;
+ uint_t ufri_slotno;
+ uint64_t ufri_offset;
+ uint64_t ufri_len;
+ uint64_t ufri_nread;
+ void *ufri_buf;
+ char ufri_devpath[MAXPATHLEN];
+} ufm_ioc_readimg_t;
.Ed
+.Pp
+The
+.Ft ufri_imageno
+and
+.Ft ufri_slotno
+values are used to indicate the image and slot to read.
+These indexes correspond to the same indices that are returned in the
+nvlist from the
+.Dv UFM_IOC_REPORT
+ioctl.
+The
+.Ft ufri_offset
+and
+.Ft ufri_len
+members are used to indicate how many bytes to read from the image and
+where in the image to begin.
+The
+.Fa ufri_buf
+member must be set to a valid pointer.
+Data read from the device will be placed in that pointer.
+The pointer must be at least
+.Fa ufri_len
+bytes long.
+Upon successful completion, the
+.Fa ufri_nread
+member will be filled in with the number of bytes that have been placed
+in
+.Fa ufri_buf .
+Finally, the
+.Fa ufri_version
+and
+.Fa ufri_devpath
+fields must be filled in with the version number,
+.Dv DDI_UFM_VERSION_ONE ,
+and the corresponding /devices path.
.El
.Sh EXAMPLES
-This example demonstrates how to use the UFM_IOC_GETCAPS ioctl to determine
-the UFM capabilities of a given device instance.
+This example demonstrates how to use the
+.Dv UFM_IOC_GETCAPS
+ioctl to determine the UFM capabilities of a given device instance.
.Bd -literal
#include <stdio.h>
#include <stdlib.h>
@@ -183,19 +286,22 @@ A subset of the more common errors are detailed below.
For a full list of error numbers, see
.Xr Intro 2
.Bl -tag -width Er
-.It Er ENOTSUP
-Either the requested ioctl is not supported by the target device, the device
-does not exist or the device does not support the UFM interfaces.
-.It Er EFAULT
-The ufm driver encountered a failure while copying data either from or to the
-address space of the calling process.
.It Er EAGAIN
The device driver is not currently ready to accept calls to it's DDI UFM entry
points.
This may be because the driver is not fully initialized or because the driver
is in the process of detaching.
+.It Er EFAULT
+The ufm driver encountered a failure while copying data either from or to the
+address space of the calling process.
+.It Er EINVAL
+The offset or length of an image would have resulted in a read outside
+of the image's valid range or with improper alignment.
.It Er EIO
A failure occurred while executing a DDI UFM entry point.
+.It Er ENOTSUP
+Either the requested ioctl is not supported by the target device, the device
+does not exist or the device does not support the UFM interfaces.
.El
.Sh INTERFACE STABILITY
.Sy Evolving
diff --git a/usr/src/man/man9e/ddi_ufm.9e b/usr/src/man/man9e/ddi_ufm.9e
index 3faa0b9eb1..2f75821c37 100644
--- a/usr/src/man/man9e/ddi_ufm.9e
+++ b/usr/src/man/man9e/ddi_ufm.9e
@@ -10,8 +10,9 @@
.\"
.\"
.\" Copyright 2019 Joyent, Inc.
+.\" Copyright 2020 Oxide Computer Company
.\"
-.Dd February 15, 2020
+.Dd May 19, 2020
.Dt DDI_UFM 9E
.Os
.Sh NAME
@@ -42,7 +43,7 @@
.Fa "ddi_ufm_handle_t *uhp"
.Fa "void *drv_arg"
.Fa "uint_t imgid"
-.Fa "ddi_ufm_image_t *uip"
+.Fa "ddi_ufm_image_t *imgp"
.Fc
.Ft int
.Fo ddi_ufm_op_fill_slot
@@ -50,7 +51,18 @@
.Fa "void *drv_arg"
.Fa "uint_t imgid"
.Fa "uint_t slotid"
-.Fa "ddi_ufm_slot_t *usp"
+.Fa "ddi_ufm_slot_t *slotp"
+.Fc
+.Ft int
+.Fo ddi_ufm_op_readimg
+.Fa "ddi_ufm_handle_t *uhp"
+.Fa "void *drv_arg"
+.Fa "uint_t imgid"
+.Fa "uint_t slotid"
+.Fa "uint64_t len"
+.Fa "uint64_t offset"
+.Fa "void *buf"
+.Fa "uint64_t *nreadp"
.Fc
.Sh INTERFACE LEVEL
.Sy Evolving - This interface is evolving still in illumos. API and ABI stability is not guaranteed.
@@ -69,12 +81,21 @@ A pointer that the driver should set with a number of images.
A pointer that the driver should set with a number of slots.
.It Fa imgid
An integer indicating which image information is being requested for.
-.It Fa uip
+.It Fa imgp
An opaque pointer that represents a UFM image.
.It Fa slotid
An integer indicating which slot information is being requested for.
-.It Fa usp
+.It Fa slotp
An opaque pointer that represents a UFM slot.
+.It Fa len
+Indicates the number of bytes from a firmware payload that are desired.
+.It Fa offset
+Indicates an offset in a firmware payload to start reading from.
+.It Fa buf
+A buffer to place data firmware data read into.
+.It Fa nreadp
+A pointer whose value should be updated with the number of bytes
+actually read from the image.
.El
.Sh DESCRIPTION
Upgradable firmware modules (UFM) are a potential component of many
@@ -156,23 +177,29 @@ a given time.
The UFM operations vector is a structure that has the following members:
.Bd -literal -offset indent
typedef struct ddi_ufm_ops {
- int (*ddi_ufm_op_nimages)(ddi_ufm_handle_t *uhp, void *arg,
+ int (*ddi_ufm_op_nimages)(ddi_ufm_handle_t *uhp, void *drv_arg,
uint_t *nimgp);
- int (*ddi_ufm_op_fill_image)(ddi_ufm_handle_t *uhp, void *arg,
- uint_t imgid, ddi_ufm_image_t *img);
- int (*ddi_ufm_op_fill_slot)(ddi_ufm_handle_t *uhp, void *arg,
+ int (*ddi_ufm_op_fill_image)(ddi_ufm_handle_t *uhp, void *drv_arg,
+ uint_t imgid, ddi_ufm_image_t *imgp);
+ int (*ddi_ufm_op_fill_slot)(ddi_ufm_handle_t *uhp, void *drv_arg,
int imgid, ddi_ufm_image_t *img, uint_t slotid,
ddi_ufm_slot_t *slotp);
- int (*ddi_ufm_op_getcaps)(ddi_ufm_handle_t *uhp, void *arg,
+ int (*ddi_ufm_op_getcaps)(ddi_ufm_handle_t *uhp, void *drv_arg,
ddi_ufm_cap_t *caps);
+ int (*ddi_ufm_op_readimg)(ddi_ufm_handle_t *uhp, void *drv_arg,
+ uint_t imgid, uint_t slotid, uint64_t len, uint64_t offset,
+ void *buf, uint64_t *nreadp);
} ddi_ufm_ops_t;
.Ed
.Pp
The
.Fn ddi_ufm_op_nimages
-entry point is optional.
+and
+.Fn ddi_ufm_op_readimg
+entry points are optional.
If a device only has a single image, then there is no reason to implement the
-.Fn ddi_ufm_op_nimages entry point.
+.Fn ddi_ufm_op_nimages
+entry point.
The system will assume that there is only a single image.
.Pp
Slots and images are numbered starting at zero.
@@ -192,7 +219,8 @@ The
entry point is an optional entry point that answers the question of how
many different, distinct firmware images are present on the device.
Once the driver determines how many are present, it should set the value in
-.Fa nimgp to the determined value.
+.Fa nimgp
+to the determined value.
.Pp
It is legal for a device to pass in zero for this value, which indicates
that there are none present.
@@ -223,7 +251,7 @@ then it should return an error.
The
.Ft ddi_ufm_image_t
structure passed in
-.Fa uip
+.Fa imgp
is opaque.
To fill in information about the image, the driver should call the functions
described in
@@ -276,7 +304,7 @@ then it should return an error.
The
.Ft ddi_ufm_slot_t
structure passed in
-.Fa usp
+.Fa slotp
is opaque.
To fill in information about the image the driver should call the functions
described in
@@ -313,10 +341,17 @@ This attributes indicates that the specified slot does not currently contain
any firmware image.
.El
.Pp
+If the driver supports the
+.Fn ddi_ufm_op_readimg
+entry point, then the driver should attempt to determine the size in
+bytes of the image in the slot and indicate that by calling the
+.Xr ddi_ufm_slot_set_imgsize 9F
+function.
+.Pp
Finally, if there are any device-specific key-value pairs that form
useful, ancillary data, then the driver should assemble an nvlist and
pass it to the
-.Xr ddi_ufm_set_misc 9F
+.Xr ddi_ufm_slot_set_misc 9F
function.
.Pp
Once the driver has finished setting all of the information about the
@@ -345,12 +380,20 @@ The
.Fn ddi_ufm_op_getcaps
function is used to indicate which DDI UFM capabilities are supported by this
driver instance.
-Currently there is only a single capability
-.Pq DDI_UFM_CAP_REPORT
-which indicates that the driver is capable of reporting UFM information for this
-instance.
-Future UFM versions may add additional capabilities such as the ability to
-obtain a raw dump of the firmware image or to upgrade the firmware.
+The following capabilities are supported and the drivers should return a
+bitwise-inclusive-OR of the following values:
+.Bl -tag -width Dv -offset width
+.It Dv DDI_UFM_CAP_REPORT
+Indicates that the driver is capable of reporting UFM information and
+implements the
+.Fn ddi_ufm_op_fill_slot
+entry point and optionally the
+.Fn ddi_ufm_op_fill_image
+entry point.
+.It Dv DDI_UFM_CAP_READIMG
+Indicates that the driver is capable of reading a binary firmware
+payload off of a device.
+.El
.Pp
The driver should indicate the supported capabilities by setting the value in
the
@@ -371,6 +414,55 @@ capabilities.
.It Er ENOMEM
The driver was unable to allocate memory.
.El
+.It Fn ddi_ufm_op_readimg
+The
+.Fn ddi_ufm_op_readimg
+is an optional entry point that allows the system to read a binary
+firmware payload from the device.
+The driver should read the firmware payload indicated by both
+.Fa imgid
+and
+.Fa slotid .
+The driver should check to make sure that the region requested, starting
+at
+.Fa offset
+bytes into the image
+and
+.Fa len
+bytes long is valid for the image and if not, return the error
+.Er EINVAL .
+Data from the device should be copied into
+.Fa buf
+and the number of bytes successfully read should be placed into
+.Fa nreadp .
+.Pp
+Upon successfully reading this data, the driver should return
+.Sy 0 .
+Otherwise the driver should return the appropriate error number.
+For a full list of error numbers, see
+.Xr Intro 2 .
+Common values are:
+.Bl -tag -width Er -offset width
+.It Er EINVAL
+The image or slot indicate by
+.Fa imgid
+and
+.Fa slotid
+is unknown.
+The combination of
+.Fa offset
+and
+.Fa len
+would overflow or read from a region of the image which is not valid.
+The device currently has an alignment restriction and the requested
+offset and length do not honor that.
+.It Er EIO
+An error occurred while communicating with the device to read the
+firmware image.
+.It Er ENOTSUP
+The driver does not support reading a firmware payload on this device or
+from a particular image and slot.
+.El
.El
.Ss Caching and Updates
The system will fetch firmware and slot information on an as-needed
diff --git a/usr/src/man/man9f/Makefile b/usr/src/man/man9f/Makefile
index 24d74a300e..dec8aa99d7 100644
--- a/usr/src/man/man9f/Makefile
+++ b/usr/src/man/man9f/Makefile
@@ -926,6 +926,7 @@ MANLINKS= AVL_NEXT.9f \
ddi_ufm_image_set_nslots.9f \
ddi_ufm_init.9f \
ddi_ufm_slot_set_attrs.9f \
+ ddi_ufm_slot_set_imgsize.9f \
ddi_ufm_slot_set_misc.9f \
ddi_ufm_slot_set_version.9f \
ddi_ufm_update.9f \
@@ -1778,6 +1779,7 @@ ddi_ufm_image_set_misc.9f := LINKSRC = ddi_ufm_image.9f
ddi_ufm_image_set_nslots.9f := LINKSRC = ddi_ufm_image.9f
ddi_ufm_init.9f := LINKSRC = ddi_ufm.9f
ddi_ufm_slot_set_attrs.9f := LINKSRC = ddi_ufm_slot.9f
+ddi_ufm_slot_set_imgsize.9f := LINKSRC = ddi_ufm_slot.9f
ddi_ufm_slot_set_misc.9f := LINKSRC = ddi_ufm_slot.9f
ddi_ufm_slot_set_version.9f := LINKSRC = ddi_ufm_slot.9f
ddi_ufm_update.9f := LINKSRC = ddi_ufm.9f
diff --git a/usr/src/man/man9f/ddi_ufm.9f b/usr/src/man/man9f/ddi_ufm.9f
index 3cb48ead7d..592f550d36 100644
--- a/usr/src/man/man9f/ddi_ufm.9f
+++ b/usr/src/man/man9f/ddi_ufm.9f
@@ -125,7 +125,7 @@ It should be used at the end of a driver's
.Xr attach 9E
endpoint to indicate that it is ready to receive UFM requests.
It should also be called whenever the driver believes that the UFM might have
-changed.
+changed or the device's capabilities.
This may happen after a device reset or firmware change.
Unlike the other functions, this can be called from any context with any locks
held, excepting high-level interrupt context which normal device drivers
diff --git a/usr/src/man/man9f/ddi_ufm_slot.9f b/usr/src/man/man9f/ddi_ufm_slot.9f
index c547905581..c8c7936b1f 100644
--- a/usr/src/man/man9f/ddi_ufm_slot.9f
+++ b/usr/src/man/man9f/ddi_ufm_slot.9f
@@ -10,15 +10,17 @@
.\"
.\"
.\" Copyright 2019 Joyent, Inc.
+.\" Copyright 2020 Oxide Computer Company
.\"
-.Dd Apr 30, 2019
+.Dd May 19, 2020
.Dt DDI_UFM_SLOT 9F
.Os
.Sh NAME
.Nm ddi_ufm_slot ,
.Nm ddi_ufm_slot_set_version ,
.Nm ddi_ufm_slot_set_attrs ,
-.Nm ddi_ufm_slot_set_misc
+.Nm ddi_ufm_slot_set_misc ,
+.Nm ddi_ufm_slot_set_imgsize
.Nd UFM slot property routines
.Sh SYNOPSIS
#include <sys/ddi_ufm.h>
@@ -38,6 +40,11 @@
.Fa "ddi_ufm_slot_t *usp"
.Fa "nvlist_t *nvl"
.Fc
+.Ft void
+.Fo ddi_ufm_slot_set_imgsize
+.Fa "ddi_ufm_slot_t *usp"
+.Fa "uint64_t len"
+.Fc
.Sh INTERFACE LEVEL
.Sy Evolving -
This interface is evolving still in illumos.
@@ -60,13 +67,16 @@ function in
.Xr ddi_ufm 9E .
.It Fa nvl
An nvlist_t with ancillary, device-specific data.
+.It Fa len
+The length in bytes of a firmware image in a slot.
.El
.Sh DESCRIPTION
The
.Fn ddi_ufm_slot_set_version ,
.Fn ddi_ufm_slot_set_attrs ,
+.Fn ddi_ufm_slot_set_misc ,
and
-.Fn ddi_ufm_slot_set_misc
+.Fn ddi_ufm_slot_set_imgsize
functions are used by device drivers to set information about a firmware
slot on the slot structure
.Fa usp
@@ -104,6 +114,13 @@ Once the driver passes the nvlist to the
.Fn ddi_ufm_slot_set_misc
function, then the driver must not manipulate or free the nvlist at all.
It is the property of the UFM subsystem.
+.Pp
+the
+.Fn ddi_ufm_slot_set_imgsize
+function is used by the driver to indicate the size of a firmware image
+in a slot.
+Consumers use this to determine the amount of data that they should read
+for a firmware image itself.
.Sh CONTEXT
These functions should only be called in the context of the
.Xr ddi_ufm_op_fill_slot 9E
diff --git a/usr/src/pkg/manifests/system-flash-fwflash.mf b/usr/src/pkg/manifests/system-flash-fwflash.mf
index 4b80bcd642..0226b7af5b 100644
--- a/usr/src/pkg/manifests/system-flash-fwflash.mf
+++ b/usr/src/pkg/manifests/system-flash-fwflash.mf
@@ -48,6 +48,7 @@ file path=usr/lib/fwflash/identify/hermon.so
file path=usr/lib/fwflash/identify/sd.so
file path=usr/lib/fwflash/identify/ses.so
file path=usr/lib/fwflash/identify/tavor.so
+file path=usr/lib/fwflash/identify/ufm.so
file path=usr/lib/fwflash/verify/hermon-MELLANOX.so
file path=usr/lib/fwflash/verify/sd-GENERIC.so
file path=usr/lib/fwflash/verify/ses-SUN.so
diff --git a/usr/src/pkg/manifests/system-kernel.man9f.inc b/usr/src/pkg/manifests/system-kernel.man9f.inc
index 3d10b9ddc9..5b3dc6d94e 100644
--- a/usr/src/pkg/manifests/system-kernel.man9f.inc
+++ b/usr/src/pkg/manifests/system-kernel.man9f.inc
@@ -926,6 +926,7 @@ link path=usr/share/man/man9f/ddi_ufm_image_set_desc.9f target=ddi_ufm_image.9f
link path=usr/share/man/man9f/ddi_ufm_image_set_misc.9f target=ddi_ufm_image.9f
link path=usr/share/man/man9f/ddi_ufm_image_set_nslots.9f target=ddi_ufm_image.9f
link path=usr/share/man/man9f/ddi_ufm_slot_set_attrs.9f target=ddi_ufm_slot.9f
+link path=usr/share/man/man9f/ddi_ufm_slot_set_imgsize.9f target=ddi_ufm_slot.9f
link path=usr/share/man/man9f/ddi_ufm_slot_set_misc.9f target=ddi_ufm_slot.9f
link path=usr/share/man/man9f/ddi_ufm_slot_set_version.9f target=ddi_ufm_slot.9f
link path=usr/share/man/man9f/ddi_umem_free.9f target=ddi_umem_alloc.9f
diff --git a/usr/src/uts/common/io/igb/igb_main.c b/usr/src/uts/common/io/igb/igb_main.c
index db74846f5c..fc6450abc8 100644
--- a/usr/src/uts/common/io/igb/igb_main.c
+++ b/usr/src/uts/common/io/igb/igb_main.c
@@ -27,6 +27,7 @@
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2013, Nexenta Systems, Inc. All rights reserved.
* Copyright 2016 Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
*/
#include "igb_sw.h"
@@ -123,6 +124,13 @@ static int igb_fm_error_cb(dev_info_t *, ddi_fm_error_t *,
static void igb_fm_init(igb_t *);
static void igb_fm_fini(igb_t *);
static void igb_release_multicast(igb_t *);
+static int igb_ufm_fill_image(ddi_ufm_handle_t *, void *arg, uint_t,
+ ddi_ufm_image_t *);
+static int igb_ufm_fill_slot(ddi_ufm_handle_t *, void *, uint_t, uint_t,
+ ddi_ufm_slot_t *);
+static int igb_ufm_getcaps(ddi_ufm_handle_t *, void *, ddi_ufm_cap_t *);
+static int igb_ufm_readimg(ddi_ufm_handle_t *, void *, uint_t, uint_t,
+ uint64_t, uint64_t, void *, uint64_t *);
char *igb_priv_props[] = {
"_eee_support",
@@ -360,6 +368,13 @@ static adapter_info_t igb_i354_cap = {
0xfff00000 /* mask for RXDCTL register */
};
+static ddi_ufm_ops_t igb_ufm_ops = {
+ .ddi_ufm_op_fill_image = igb_ufm_fill_image,
+ .ddi_ufm_op_fill_slot = igb_ufm_fill_slot,
+ .ddi_ufm_op_getcaps = igb_ufm_getcaps,
+ .ddi_ufm_op_readimg = igb_ufm_readimg
+};
+
/*
* Module Initialization Functions
*/
@@ -591,6 +606,21 @@ igb_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
}
igb->attach_progress |= ATTACH_PROGRESS_ENABLE_INTR;
+ /*
+ * Only enable UFM support on function zero of the device as the images
+ * are always device wide.
+ */
+ if (igb->hw.bus.func == 0) {
+ if (ddi_ufm_init(devinfo, DDI_UFM_CURRENT_VERSION, &igb_ufm_ops,
+ &igb->igb_ufmh, igb) != 0) {
+ igb_log(igb, IGB_LOG_ERROR, "Failed to enable DDI UFM "
+ "support");
+ goto attach_fail;
+ }
+ igb->attach_progress |= ATTACH_PROGRESS_UFM;
+ ddi_ufm_update(igb->igb_ufmh);
+ }
+
igb_log(igb, IGB_LOG_INFO, "%s", igb_version);
atomic_or_32(&igb->igb_state, IGB_INITIALIZED);
@@ -743,6 +773,10 @@ igb_quiesce(dev_info_t *devinfo)
static void
igb_unconfigure(dev_info_t *devinfo, igb_t *igb)
{
+ if (igb->attach_progress & ATTACH_PROGRESS_UFM) {
+ ddi_ufm_fini(igb->igb_ufmh);
+ }
+
/*
* Disable interrupt
*/
@@ -5366,3 +5400,140 @@ igb_fm_ereport(igb_t *igb, char *detail)
FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0, NULL);
}
}
+
+static int
+igb_ufm_fill_image(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ ddi_ufm_image_t *imgp)
+{
+ igb_t *igb = arg;
+ const char *type;
+
+ if (imgno != 0) {
+ return (EINVAL);
+ }
+
+ ddi_ufm_image_set_desc(imgp, "NVM");
+ ddi_ufm_image_set_nslots(imgp, 1);
+ switch (igb->hw.nvm.type) {
+ case e1000_nvm_eeprom_spi:
+ type = "SPI EEPROM";
+ break;
+ case e1000_nvm_eeprom_microwire:
+ type = "Microwire EEPROM";
+ break;
+ case e1000_nvm_invm:
+ type = "Internal NVM";
+ break;
+ case e1000_nvm_flash_hw:
+ case e1000_nvm_flash_sw:
+ type = "Flash";
+ break;
+ default:
+ type = NULL;
+ break;
+ }
+
+ if (type != NULL) {
+ nvlist_t *nvl;
+
+ nvl = fnvlist_alloc();
+ fnvlist_add_string(nvl, "image-type", type);
+ /*
+ * The DDI takes ownership of the nvlist_t at this point.
+ */
+ ddi_ufm_image_set_misc(imgp, nvl);
+ }
+
+ return (0);
+}
+
+static int
+igb_ufm_fill_slot(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ uint_t slotno, ddi_ufm_slot_t *slotp)
+{
+ igb_t *igb = arg;
+ char *ver;
+
+ if (imgno != 0 || slotno != 0) {
+ return (EINVAL);
+ }
+
+ if (ddi_prop_lookup_string(DDI_DEV_T_ANY, igb->dip, DDI_PROP_DONTPASS,
+ "nvm-version", &ver) == 0) {
+ ddi_ufm_slot_set_version(slotp, ver);
+ ddi_prop_free(ver);
+ }
+
+ ddi_ufm_slot_set_attrs(slotp, DDI_UFM_ATTR_ACTIVE |
+ DDI_UFM_ATTR_READABLE | DDI_UFM_ATTR_WRITEABLE);
+ ddi_ufm_slot_set_imgsize(slotp, igb->hw.nvm.word_size * 2);
+ return (0);
+}
+
+static int
+igb_ufm_getcaps(ddi_ufm_handle_t *ufmh, void *arg, ddi_ufm_cap_t *caps)
+{
+ igb_t *igb = arg;
+
+ *caps = 0;
+ if (igb->hw.nvm.type != e1000_nvm_none &&
+ igb->hw.nvm.type != e1000_nvm_unknown) {
+ *caps |= DDI_UFM_CAP_REPORT;
+
+ if (igb->hw.nvm.ops.read != NULL) {
+ *caps |= DDI_UFM_CAP_READIMG;
+ }
+ }
+
+ return (0);
+}
+
+static int
+igb_ufm_readimg(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno, uint_t slotno,
+ uint64_t len, uint64_t offset, void *buf, uint64_t *nread)
+{
+ igb_t *igb = arg;
+ uint16_t wordoff, nwords, *buf16 = buf;
+ uint32_t imgsize = igb->hw.nvm.word_size * 2;
+ int ret;
+
+ if (imgno != 0 || slotno != 0) {
+ return (EINVAL);
+ }
+
+ if (len > imgsize || offset > imgsize || len + offset > imgsize) {
+ return (EINVAL);
+ }
+
+ if (igb->hw.nvm.ops.read == NULL) {
+ return (ENOTSUP);
+ }
+
+ /*
+ * Hardware provides us a means to read 16-bit words. For the time
+ * being, restrict offset and length to be 2 byte aligned. We should
+ * probably reduce this restriction. We could probably just use a bounce
+ * buffer.
+ */
+ if ((offset % 2) != 0 || (len % 2) != 0) {
+ return (EINVAL);
+ }
+
+ wordoff = offset >> 1;
+ nwords = len >> 1;
+ mutex_enter(&igb->gen_lock);
+ ret = e1000_read_nvm(&igb->hw, wordoff, nwords, buf16);
+ mutex_exit(&igb->gen_lock);
+
+ if (ret == 0) {
+ uint16_t i;
+ *nread = len;
+ for (i = 0; i < nwords; i++) {
+ buf16[i] = LE_16(buf16[i]);
+ }
+ } else {
+ ret = EIO;
+ }
+
+ return (ret);
+}
diff --git a/usr/src/uts/common/io/igb/igb_sw.h b/usr/src/uts/common/io/igb/igb_sw.h
index 2e329d6fdc..d1c80672e0 100644
--- a/usr/src/uts/common/io/igb/igb_sw.h
+++ b/usr/src/uts/common/io/igb/igb_sw.h
@@ -27,6 +27,7 @@
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2014 Pluribus Networks Inc.
* Copyright (c) 2017, Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
*/
#ifndef _IGB_SW_H
@@ -71,6 +72,7 @@ extern "C" {
#include <sys/fm/protocol.h>
#include <sys/fm/util.h>
#include <sys/fm/io/ddi.h>
+#include <sys/ddi_ufm.h>
#include "e1000_api.h"
#include "e1000_82575.h"
@@ -191,6 +193,7 @@ extern "C" {
#define ATTACH_PROGRESS_MAC 0x0800 /* MAC registered */
#define ATTACH_PROGRESS_ENABLE_INTR 0x1000 /* DDI interrupts enabled */
#define ATTACH_PROGRESS_FMINIT 0x2000 /* FMA initialized */
+#define ATTACH_PROGRESS_UFM 0x4000 /* UFM enabled */
#define PROP_ADV_AUTONEG_CAP "adv_autoneg_cap"
#define PROP_ADV_1000FDX_CAP "adv_1000fdx_cap"
@@ -733,6 +736,7 @@ typedef struct igb {
int fm_capabilities;
ulong_t page_size;
+ ddi_ufm_handle_t *igb_ufmh;
} igb_t;
typedef struct igb_stat {
diff --git a/usr/src/uts/common/io/ufm.c b/usr/src/uts/common/io/ufm.c
index 49a98c101a..d124485e65 100644
--- a/usr/src/uts/common/io/ufm.c
+++ b/usr/src/uts/common/io/ufm.c
@@ -11,6 +11,7 @@
/*
* Copyright 2019 Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
*/
/*
@@ -27,6 +28,9 @@
#include <sys/file.h>
#include <sys/kmem.h>
#include <sys/stat.h>
+#include <sys/sysmacros.h>
+
+#define UFM_READ_SIZE (1 * 1024 * 1024)
#define UFMTEST_IOC ('u' << 24) | ('f' << 16) | ('t' << 8)
#define UFMTEST_IOC_SETFW (UFMTEST_IOC | 1)
@@ -145,6 +149,7 @@ ufm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
if (devi != NULL)
ddi_remove_minor_node(devi, NULL);
+ ufm_devi = NULL;
return (DDI_SUCCESS);
}
@@ -454,6 +459,101 @@ ufm_do_report(intptr_t data, int mode)
}
static int
+ufm_do_readimg(intptr_t data, int mode)
+{
+ int ret;
+ uint_t model;
+ ufm_ioc_readimg_t ufri;
+ char devpath[MAXPATHLEN];
+ ddi_ufm_handle_t *ufmh;
+ dev_info_t *dip;
+#ifdef _MULTI_DATAMODEL
+ ufm_ioc_readimg32_t ufri32;
+#endif
+
+ model = ddi_model_convert_from(mode);
+ switch (model) {
+#ifdef _MULTI_DATAMODEL
+ case DDI_MODEL_ILP32:
+ if (ddi_copyin((void *)data, &ufri32, sizeof (ufri32),
+ mode) != 0) {
+ return (EFAULT);
+ }
+ ufri.ufri_version = ufri32.ufri_version;
+ ufri.ufri_imageno = ufri32.ufri_imageno;
+ ufri.ufri_slotno = ufri32.ufri_slotno;
+ ufri.ufri_offset = ufri32.ufri_offset;
+ ufri.ufri_len = ufri32.ufri_len;
+ ufri.ufri_nread = ufri32.ufri_nread;
+
+ if (strlcpy(ufri.ufri_devpath, ufri32.ufri_devpath,
+ MAXPATHLEN) >= MAXPATHLEN) {
+ return (EOVERFLOW);
+ }
+ ufri.ufri_buf = (caddr_t)(uintptr_t)ufri32.ufri_buf;
+ break;
+#endif /* _MULTI_DATAMODEL */
+ case DDI_MODEL_NONE:
+ default:
+ if (ddi_copyin((void *)data, &ufri, sizeof (ufri), mode) != 0) {
+ return (EFAULT);
+ }
+ }
+
+ if (strlcpy(devpath, ufri.ufri_devpath, MAXPATHLEN) >= MAXPATHLEN)
+ return (EOVERFLOW);
+
+ if ((dip = e_ddi_hold_devi_by_path(devpath, 0)) == NULL) {
+ return (ENOTSUP);
+ }
+ if ((ufmh = ufm_find(devpath)) == NULL) {
+ ddi_release_devi(dip);
+ return (ENOTSUP);
+ }
+ ASSERT(MUTEX_HELD(&ufmh->ufmh_lock));
+
+ if (!ufm_driver_ready(ufmh)) {
+ ret = EAGAIN;
+ goto out;
+ }
+
+ if (ufri.ufri_version != ufmh->ufmh_version) {
+ ret = ENOTSUP;
+ goto out;
+ }
+
+ ret = ufm_read_img(ufmh, ufri.ufri_imageno, ufri.ufri_slotno,
+ ufri.ufri_len, ufri.ufri_offset, (uintptr_t)ufri.ufri_buf,
+ &ufri.ufri_nread, mode);
+
+out:
+ mutex_exit(&ufmh->ufmh_lock);
+ ddi_release_devi(dip);
+
+ if (ret == 0) {
+ switch (model) {
+#ifdef _MULTI_DATAMODEL
+ case DDI_MODEL_ILP32:
+ ufri32.ufri_nread = ufri.ufri_nread;
+ if (ddi_copyout(&ufri32, (void *)data, sizeof (ufri32),
+ mode) != 0) {
+ return (EFAULT);
+ }
+ break;
+#endif /* _MULTI_DATAMODEL */
+ case DDI_MODEL_NONE:
+ default:
+ if (ddi_copyout(&ufri, (void *)data, sizeof (ufri),
+ mode) != 0) {
+ return (EFAULT);
+ }
+ }
+ }
+
+ return (ret);
+}
+
+static int
ufm_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp,
int *rvalp)
{
@@ -474,6 +574,10 @@ ufm_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp,
case UFM_IOC_REPORT:
ret = ufm_do_report(data, mode);
break;
+
+ case UFM_IOC_READIMG:
+ ret = ufm_do_readimg(data, mode);
+ break;
default:
return (ENOTTY);
}
diff --git a/usr/src/uts/common/mapfiles/ddi.mapfile b/usr/src/uts/common/mapfiles/ddi.mapfile
index 9b6a9ab677..e14e412881 100644
--- a/usr/src/uts/common/mapfiles/ddi.mapfile
+++ b/usr/src/uts/common/mapfiles/ddi.mapfile
@@ -139,6 +139,7 @@ SYMBOL_SCOPE {
ddi_ufm_image_set_nslots { FLAGS = EXTERN };
ddi_ufm_init { FLAGS = EXTERN };
ddi_ufm_slot_set_attrs { FLAGS = EXTERN };
+ ddi_ufm_slot_set_imgsize { FLAGS = EXTERN };
ddi_ufm_slot_set_misc { FLAGS = EXTERN };
ddi_ufm_slot_set_version { FLAGS = EXTERN };
ddi_ufm_update { FLAGS = EXTERN };
diff --git a/usr/src/uts/common/mapfiles/kernel.mapfile b/usr/src/uts/common/mapfiles/kernel.mapfile
index 6bddb3c7ef..27b6345582 100644
--- a/usr/src/uts/common/mapfiles/kernel.mapfile
+++ b/usr/src/uts/common/mapfiles/kernel.mapfile
@@ -38,4 +38,6 @@ SYMBOL_SCOPE {
global:
bt_getlowbit { FLAGS = EXTERN };
servicing_interrupt { FLAGS = EXTERN };
+ fnvlist_alloc { FLAGS = EXTERN };
+ fnvlist_add_string { FLAGS = EXTERN };
};
diff --git a/usr/src/uts/common/os/ddi_ufm.c b/usr/src/uts/common/os/ddi_ufm.c
index ffb04eddec..7e863bdac2 100644
--- a/usr/src/uts/common/os/ddi_ufm.c
+++ b/usr/src/uts/common/os/ddi_ufm.c
@@ -11,6 +11,7 @@
/*
* Copyright 2019 Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
*/
#include <sys/avl.h>
@@ -20,6 +21,9 @@
#include <sys/kmem.h>
#include <sys/sunddi.h>
#include <sys/stddef.h>
+#include <sys/sunndi.h>
+#include <sys/file.h>
+#include <sys/sysmacros.h>
/*
* The UFM subsystem tracks its internal state with respect to device
@@ -65,6 +69,12 @@
* These tests should be run whenever changes are made to the DDI UFM
* subsystem or the ufm driver.
*/
+
+/*
+ * Amount of data to read in one go (1 MiB).
+ */
+#define UFM_READ_STRIDE (1024 * 1024)
+
static avl_tree_t ufm_handles;
static kmutex_t ufm_lock;
@@ -234,6 +244,12 @@ ufm_cache_fill(ddi_ufm_handle_t *ufmh)
if (slot->ufms_attrs & DDI_UFM_ATTR_EMPTY)
continue;
+ if (slot->ufms_imgsize != 0) {
+ fnvlist_add_uint64(slots[s],
+ DDI_UFM_NV_SLOT_IMGSIZE,
+ slot->ufms_imgsize);
+ }
+
fnvlist_add_string(slots[s], DDI_UFM_NV_SLOT_VERSION,
slot->ufms_version);
if (slot->ufms_misc != NULL) {
@@ -257,6 +273,56 @@ cache_fail:
return (ret);
}
+int
+ufm_read_img(ddi_ufm_handle_t *ufmh, uint_t img, uint_t slot, uint64_t len,
+ uint64_t off, uintptr_t uaddr, uint64_t *nreadp, int copyflags)
+{
+ int ret = 0;
+ ddi_ufm_cap_t caps;
+ void *buf;
+ uint64_t nread;
+
+ ret = ufmh->ufmh_ops->ddi_ufm_op_getcaps(ufmh, ufmh->ufmh_arg, &caps);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ if ((caps & DDI_UFM_CAP_READIMG) == 0 ||
+ ufmh->ufmh_ops->ddi_ufm_op_readimg == NULL) {
+ return (ENOTSUP);
+ }
+
+ if (off + len < MAX(off, len)) {
+ return (EOVERFLOW);
+ }
+
+ buf = kmem_zalloc(UFM_READ_STRIDE, KM_SLEEP);
+ nread = 0;
+ while (len > 0) {
+ uint64_t toread = MIN(len, UFM_READ_STRIDE);
+ uint64_t iter;
+
+ ret = ufmh->ufmh_ops->ddi_ufm_op_readimg(ufmh, ufmh->ufmh_arg,
+ img, slot, toread, off + nread, buf, &iter);
+ if (ret != 0) {
+ break;
+ }
+
+ if (ddi_copyout(buf, (void *)(uintptr_t)(uaddr + nread), iter,
+ copyflags & FKIOCTL) != 0) {
+ ret = EFAULT;
+ break;
+ }
+
+ nread += iter;
+ len -= iter;
+ }
+
+ *nreadp = nread;
+ kmem_free(buf, UFM_READ_STRIDE);
+ return (ret);
+}
+
/*
* This gets called early in boot by setup_ddi().
*/
@@ -375,6 +441,12 @@ ddi_ufm_init(dev_info_t *dip, uint_t version, ddi_ufm_ops_t *ufmops,
mutex_exit(&old_ufmh->ufmh_lock);
}
+ /*
+ * Give a hint in the devinfo tree that this device supports UFM
+ * capabilities.
+ */
+ (void) ndi_prop_create_boolean(DDI_DEV_T_NONE, dip, "ddi-ufm-capable");
+
return (DDI_SUCCESS);
}
@@ -453,3 +525,10 @@ ddi_ufm_slot_set_misc(ddi_ufm_slot_t *usp, nvlist_t *misc)
nvlist_free(usp->ufms_misc);
usp->ufms_misc = misc;
}
+
+void
+ddi_ufm_slot_set_imgsize(ddi_ufm_slot_t *usp, uint64_t size)
+{
+ VERIFY3P(usp, !=, NULL);
+ usp->ufms_imgsize = size;
+}
diff --git a/usr/src/uts/common/sys/ddi_ufm.h b/usr/src/uts/common/sys/ddi_ufm.h
index 42d8ff72e6..9f2474f2d3 100644
--- a/usr/src/uts/common/sys/ddi_ufm.h
+++ b/usr/src/uts/common/sys/ddi_ufm.h
@@ -11,6 +11,7 @@
/*
* Copyright 2020 Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
*/
#ifndef _SYS_DDI_UFM_H
@@ -39,19 +40,19 @@ extern "C" {
#define UFM_IOC_GETCAPS (UFM_IOC | 1)
#define UFM_IOC_REPORTSZ (UFM_IOC | 2)
#define UFM_IOC_REPORT (UFM_IOC | 3)
+#define UFM_IOC_READIMG (UFM_IOC | 4)
#define UFM_IOC_MAX UFM_IOC_REPORT
/*
* Bitfield enumerating the DDI UFM capabilities supported by this device
* instance. Currently there is only a single capability of being able to
- * report UFM information. Future UFM versions may add additional capabilities
- * such as the ability to obtain a raw dump the firmware image or ability to
- * upgrade the firmware. When support for new capabilties are added to the DDI
- * UFM subsystem, it should be reflected in this enum and the implementation of
- * the UFM_IOC_GETCAPS should be extended appropriately.
+ * report UFM information. When support for new capabilties are added to the
+ * DDI UFM subsystem, it should be reflected in this enum and the implementation
+ * of the UFM_IOC_GETCAPS should be extended appropriately.
*/
typedef enum {
- DDI_UFM_CAP_REPORT = 1 << 0
+ DDI_UFM_CAP_REPORT = 1 << 0,
+ DDI_UFM_CAP_READIMG = 1 << 1
} ddi_ufm_cap_t;
/*
@@ -111,6 +112,36 @@ typedef struct ufm_ioc_report32 {
#endif /* _KERNEL */
/*
+ * This struct defines the input/output data for the UFM_IOC_READ ioctl, which
+ * reads the firmware image from a given slot.
+ */
+typedef struct ufm_ioc_readimg {
+ uint_t ufri_version;
+ uint_t ufri_imageno;
+ uint_t ufri_slotno;
+ uint64_t ufri_offset;
+ uint64_t ufri_len;
+ uint64_t ufri_nread;
+ void *ufri_buf;
+ char ufri_devpath[MAXPATHLEN];
+} ufm_ioc_readimg_t;
+
+#ifdef _KERNEL
+#pragma pack(4)
+typedef struct ufm_ioc_readimg32 {
+ uint_t ufri_version;
+ uint_t ufri_imageno;
+ uint_t ufri_slotno;
+ uint64_t ufri_offset;
+ uint64_t ufri_len;
+ uint64_t ufri_nread;
+ caddr32_t ufri_buf;
+ char ufri_devpath[MAXPATHLEN];
+} ufm_ioc_readimg32_t;
+#pragma pack()
+#endif /* _KERNEL */
+
+/*
* The UFM_IOC_REPORT ioctl return UFM image and slot data in the form of a
* packed nvlist. The nvlist contains and array of nvlists (one-per-image).
* Each image nvlist contains will contain a string nvpair containing a
@@ -127,12 +158,18 @@ typedef struct ufm_ioc_report32 {
#define DDI_UFM_NV_IMAGE_SLOTS "ufm-image-slots"
/*
- * Each slot nvlist as a string nvpair describing the firmware image version
- * and an uint32 nvpair describing the slot attributes (see ddi_ufm_attr_t
- * above). An option nvlist nvpar may be present containing additional
- * miscellaneous slot data.
+ * Each slot nvlist has the following:
+ *
+ * o A string nvpair describing the firmware image version
+ * o A uint32 nvpair describing the slot attributes (see ddi_ufm_attr_t
+ * below).
+ * o An optional nvlist nvpar may be present containing additional
+ * miscellaneous slot data.
+ * o An optional uint64 slot length that indicates the size of the image in
+ * that slot. Note htis is the size of the image, not the size of the slot.
*/
#define DDI_UFM_NV_SLOT_VERSION "ufm-slot-version"
+#define DDI_UFM_NV_SLOT_IMGSIZE "ufm-slot-imgsize"
typedef enum {
DDI_UFM_ATTR_READABLE = 1 << 0,
@@ -166,6 +203,8 @@ typedef struct ddi_ufm_ops {
int (*ddi_ufm_op_fill_slot)(ddi_ufm_handle_t *, void *, uint_t, uint_t,
ddi_ufm_slot_t *);
int (*ddi_ufm_op_getcaps)(ddi_ufm_handle_t *, void *, ddi_ufm_cap_t *);
+ int (*ddi_ufm_op_readimg)(ddi_ufm_handle_t *, void *, uint_t, uint_t,
+ uint64_t, uint64_t, void *, uint64_t *);
} ddi_ufm_ops_t;
/*
@@ -215,6 +254,7 @@ void ddi_ufm_image_set_misc(ddi_ufm_image_t *, nvlist_t *);
void ddi_ufm_slot_set_version(ddi_ufm_slot_t *, const char *);
void ddi_ufm_slot_set_attrs(ddi_ufm_slot_t *, ddi_ufm_attr_t);
void ddi_ufm_slot_set_misc(ddi_ufm_slot_t *, nvlist_t *);
+void ddi_ufm_slot_set_imgsize(ddi_ufm_slot_t *, uint64_t);
#endif /* _KERNEL */
#ifdef __cplusplus
diff --git a/usr/src/uts/common/sys/ddi_ufm_impl.h b/usr/src/uts/common/sys/ddi_ufm_impl.h
index 49fd3b9246..8570f7f11e 100644
--- a/usr/src/uts/common/sys/ddi_ufm_impl.h
+++ b/usr/src/uts/common/sys/ddi_ufm_impl.h
@@ -11,6 +11,7 @@
/*
* Copyright 2019 Joyent, Inc.
+ * Copyright 2020 Oxide Computer Company
*/
#ifndef _SYS_DDI_UFM_IMPL_H
@@ -38,11 +39,14 @@ void ufm_init();
/* private interfaces for ufm driver */
struct ddi_ufm_handle *ufm_find(const char *);
int ufm_cache_fill(struct ddi_ufm_handle *ufmh);
+int ufm_read_img(ddi_ufm_handle_t *, uint_t, uint_t, uint64_t, uint64_t,
+ uintptr_t, uint64_t *, int);
struct ddi_ufm_slot {
uint_t ufms_slotno;
char *ufms_version;
ddi_ufm_attr_t ufms_attrs;
+ uint64_t ufms_imgsize;
nvlist_t *ufms_misc;
};
diff --git a/usr/src/uts/intel/igb/Makefile b/usr/src/uts/intel/igb/Makefile
index 72d4b205b3..50e147706d 100644
--- a/usr/src/uts/intel/igb/Makefile
+++ b/usr/src/uts/intel/igb/Makefile
@@ -69,7 +69,7 @@ INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
# Driver depends on MAC
#
LDFLAGS += -dy -N misc/mac
-MAPFILES += ddi mac random
+MAPFILES += ddi mac random kernel
#
# Default build targets.