summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Johnston <rob.johnston@joyent.com>2019-02-19 19:46:39 +0000
committerRichard Lowe <richlowe@richlowe.net>2019-07-26 16:34:02 +0000
commit508a0e8cf1600b06c1f7361ad76e736710d3fdf8 (patch)
treeb0210a4c46ddc28f53e733f51476a58fb9a81606
parent391889ecff3f697040bc0677f3fb4a002d562e31 (diff)
downloadillumos-joyent-508a0e8cf1600b06c1f7361ad76e736710d3fdf8.tar.gz
11257 Add DDI support for Upgradable Firmware Modules
11258 Add libtopo support for Upgradable Firmware Modules Reviewed by: Robert Mustacchi <robert.mustacchi@joyent.com> Reviewed by: Patrick Mooney <patrick.mooney@joyent.com> Reviewed by: Toomas Soome <tsoome@me.com> Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com> Approved by: Richard Lowe <richlowe@richlowe.net>
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/hc.c1
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/libtopo.h27
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/mapfile-vers2
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_hc.h10
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_mod.c224
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_mod.h5
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_mod.map3
-rw-r--r--usr/src/lib/fm/topo/modules/common/disk/disk_common.c20
-rw-r--r--usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c201
-rw-r--r--usr/src/lib/fm/topo/modules/common/smbios/smbios_enum.c51
-rw-r--r--usr/src/man/man7d/Makefile1
-rw-r--r--usr/src/man/man7d/ufm.7d206
-rw-r--r--usr/src/man/man9e/Makefile13
-rw-r--r--usr/src/man/man9e/ddi_ufm.9e421
-rw-r--r--usr/src/man/man9f/Makefile46
-rw-r--r--usr/src/man/man9f/ddi_ufm.9f162
-rw-r--r--usr/src/man/man9f/ddi_ufm_image.9f102
-rw-r--r--usr/src/man/man9f/ddi_ufm_slot.9f111
-rw-r--r--usr/src/pkg/manifests/system-header.mf2
-rw-r--r--usr/src/pkg/manifests/system-io-tests.mf3
-rw-r--r--usr/src/pkg/manifests/system-kernel.man7d.inc2
-rw-r--r--usr/src/pkg/manifests/system-kernel.man9e.inc6
-rw-r--r--usr/src/pkg/manifests/system-kernel.man9f.inc13
-rw-r--r--usr/src/pkg/manifests/system-kernel.mf4
-rw-r--r--usr/src/pkg/manifests/system-test-ostest.mf7
-rw-r--r--usr/src/test/os-tests/runfiles/default.run6
-rw-r--r--usr/src/test/os-tests/tests/Makefile16
-rw-r--r--usr/src/test/os-tests/tests/ddi_ufm/Makefile51
-rw-r--r--usr/src/test/os-tests/tests/ddi_ufm/ufm-test-cleanup.sh32
-rw-r--r--usr/src/test/os-tests/tests/ddi_ufm/ufm-test-setup.sh34
-rw-r--r--usr/src/test/os-tests/tests/ddi_ufm/ufm-test.c897
-rw-r--r--usr/src/uts/common/Makefile.files1
-rw-r--r--usr/src/uts/common/io/i40e/i40e_main.c112
-rw-r--r--usr/src/uts/common/io/i40e/i40e_sw.h21
-rw-r--r--usr/src/uts/common/io/scsi/adapters/mpt_sas/mptsas.c79
-rw-r--r--usr/src/uts/common/io/ufm.c482
-rw-r--r--usr/src/uts/common/io/ufm.conf16
-rw-r--r--usr/src/uts/common/io/ufmtest.c442
-rw-r--r--usr/src/uts/common/io/ufmtest.conf16
-rw-r--r--usr/src/uts/common/io/ufmtest.h75
-rw-r--r--usr/src/uts/common/mapfiles/ddi.mapfile15
-rw-r--r--usr/src/uts/common/os/autoconf.c5
-rw-r--r--usr/src/uts/common/os/ddi_ufm.c452
-rw-r--r--usr/src/uts/common/sys/Makefile4
-rw-r--r--usr/src/uts/common/sys/ddi_ufm.h224
-rw-r--r--usr/src/uts/common/sys/ddi_ufm_impl.h87
-rw-r--r--usr/src/uts/common/sys/scsi/adapters/mpt_sas/mptsas_var.h6
-rw-r--r--usr/src/uts/intel/Makefile.intel2
-rw-r--r--usr/src/uts/intel/ufm/Makefile48
-rw-r--r--usr/src/uts/intel/ufmtest/Makefile48
-rw-r--r--usr/src/uts/sparc/Makefile.sparc4
-rw-r--r--usr/src/uts/sparc/ufm/Makefile48
-rw-r--r--usr/src/uts/sparc/ufmtest/Makefile48
53 files changed, 4854 insertions, 60 deletions
diff --git a/usr/src/lib/fm/topo/libtopo/common/hc.c b/usr/src/lib/fm/topo/libtopo/common/hc.c
index 15d7b769e9..3101d12d56 100644
--- a/usr/src/lib/fm/topo/libtopo/common/hc.c
+++ b/usr/src/lib/fm/topo/libtopo/common/hc.c
@@ -206,6 +206,7 @@ static const hcc_t hc_canon[] = {
{ SUBCHASSIS, TOPO_STABILITY_PRIVATE },
{ SYSTEMBOARD, TOPO_STABILITY_PRIVATE },
{ TRANSCEIVER, TOPO_STABILITY_PRIVATE },
+ { UFM, TOPO_STABILITY_PRIVATE },
{ USB_DEVICE, TOPO_STABILITY_PRIVATE },
{ XAUI, TOPO_STABILITY_PRIVATE },
{ XFP, TOPO_STABILITY_PRIVATE }
diff --git a/usr/src/lib/fm/topo/libtopo/common/libtopo.h b/usr/src/lib/fm/topo/libtopo/common/libtopo.h
index b230510afd..a5d9e66e06 100644
--- a/usr/src/lib/fm/topo/libtopo/common/libtopo.h
+++ b/usr/src/lib/fm/topo/libtopo/common/libtopo.h
@@ -1016,9 +1016,34 @@ typedef enum topo_led_type {
} topo_led_type_t;
typedef enum topo_slot_type {
- TOPO_SLOT_TYPE_DIMM = 1
+ TOPO_SLOT_TYPE_DIMM = 1,
+ TOPO_SLOT_TYPE_UFM
} topo_slot_type_t;
+/*
+ * Read permission indicates that we can read the raw firmware image in this
+ * slot off of the device.
+ *
+ * Write permission indicates that we can write a firmware image into this
+ * slot.
+ *
+ * These permission are orthogonal to the ability to simply report information
+ * about the firmware image in a slot.
+ */
+typedef enum topo_ufm_slot_mode {
+ TOPO_UFM_SLOT_MODE_NONE = 1,
+ TOPO_UFM_SLOT_MODE_RO,
+ TOPO_UFM_SLOT_MODE_WO,
+ TOPO_UFM_SLOT_MODE_RW
+} topo_ufm_slot_mode_t;
+
+typedef struct topo_ufm_slot_info {
+ uint32_t usi_slotid;
+ topo_ufm_slot_mode_t usi_mode;
+ const char *usi_version;
+ boolean_t usi_active;
+ nvlist_t *usi_extra;
+} topo_ufm_slot_info_t;
#ifdef __cplusplus
}
diff --git a/usr/src/lib/fm/topo/libtopo/common/mapfile-vers b/usr/src/lib/fm/topo/libtopo/common/mapfile-vers
index c7b02e9958..ccb0d89494 100644
--- a/usr/src/lib/fm/topo/libtopo/common/mapfile-vers
+++ b/usr/src/lib/fm/topo/libtopo/common/mapfile-vers
@@ -93,6 +93,8 @@ SYMBOL_VERSION SUNWprivate {
topo_mod_clean_str;
topo_mod_clrdebug;
topo_mod_cpufmri;
+ topo_mod_create_ufm;
+ topo_mod_create_ufm_slot;
topo_mod_devfmri;
topo_mod_devinfo;
topo_mod_dprintf;
diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_hc.h b/usr/src/lib/fm/topo/libtopo/common/topo_hc.h
index addc96803f..e8e80d54b0 100644
--- a/usr/src/lib/fm/topo/libtopo/common/topo_hc.h
+++ b/usr/src/lib/fm/topo/libtopo/common/topo_hc.h
@@ -94,6 +94,7 @@ extern "C" {
#define SUBCHASSIS "subchassis"
#define SYSTEMBOARD "systemboard"
#define TRANSCEIVER "transceiver"
+#define UFM "ufm"
#define USB_DEVICE "usb-device"
#define XAUI "xaui"
#define XFP "xfp"
@@ -250,9 +251,16 @@ extern "C" {
#define TOPO_PROP_MB_PRODUCT "product-id"
#define TOPO_PROP_MB_ASSET "asset-tag"
#define TOPO_PROP_MB_FIRMWARE_VENDOR "firmware-vendor"
-#define TOPO_PROP_MB_FIRMWARE_REV "firmware-revision"
#define TOPO_PROP_MB_FIRMWARE_RELDATE "firmware-release-date"
+#define TOPO_PGROUP_UFM "ufm-properties"
+#define TOPO_PROP_UFM_DESCR "ufm-description"
+
+#define TOPO_PGROUP_UFM_SLOT "ufm-slot-properties"
+#define TOPO_PROP_UFM_SLOT_VERSION "ufm-slot-version"
+#define TOPO_PROP_UFM_SLOT_MODE "ufm-slot-mode"
+#define TOPO_PROP_UFM_SLOT_ACTIVE "ufm-slot-active"
+
#ifdef __cplusplus
}
#endif
diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_mod.c b/usr/src/lib/fm/topo/libtopo/common/topo_mod.c
index 9c6f52ee00..851f0264cd 100644
--- a/usr/src/lib/fm/topo/libtopo/common/topo_mod.c
+++ b/usr/src/lib/fm/topo/libtopo/common/topo_mod.c
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2019, Joyent, Inc.
+ * Copyright 2019 Joyent, Inc.
*/
/*
@@ -971,3 +971,225 @@ topo_mod_hc_occupied(topo_mod_t *mod, tnode_t *node, topo_version_t version,
return (0);
}
+
+/*
+ * Convenience routine for creating a UFM slot node. This routine assumes
+ * that the caller has already created the containing range via a call to
+ * topo_node_range_create().
+ */
+tnode_t *
+topo_mod_create_ufm_slot(topo_mod_t *mod, tnode_t *ufmnode,
+ topo_ufm_slot_info_t *slotinfo)
+{
+ nvlist_t *auth = NULL, *fmri = NULL;
+ tnode_t *slotnode;
+ topo_pgroup_info_t pgi;
+ int err, rc;
+
+ if (slotinfo == NULL || slotinfo->usi_version == NULL ||
+ slotinfo->usi_mode == 0) {
+ topo_mod_dprintf(mod, "invalid slot info");
+ (void) topo_mod_seterrno(mod, ETOPO_MOD_INVAL);
+ return (NULL);
+ }
+ if ((auth = topo_mod_auth(mod, ufmnode)) == NULL) {
+ topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
+ topo_mod_errmsg(mod));
+ /* errno set */
+ return (NULL);
+ }
+
+ if ((fmri = topo_mod_hcfmri(mod, ufmnode, FM_HC_SCHEME_VERSION,
+ SLOT, slotinfo->usi_slotid, NULL, auth, NULL, NULL, NULL)) ==
+ NULL) {
+ nvlist_free(auth);
+ topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
+ topo_mod_errmsg(mod));
+ /* errno set */
+ return (NULL);
+ }
+
+ if ((slotnode = topo_node_bind(mod, ufmnode, SLOT,
+ slotinfo->usi_slotid, fmri)) == NULL) {
+ nvlist_free(auth);
+ nvlist_free(fmri);
+ topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
+ topo_mod_errmsg(mod));
+ /* errno set */
+ return (NULL);
+ }
+
+ /* Create authority and system pgroups */
+ topo_pgroup_hcset(slotnode, auth);
+ nvlist_free(auth);
+ nvlist_free(fmri);
+
+ /* Just inherit the parent's FRU */
+ if (topo_node_fru_set(slotnode, NULL, 0, &err) != 0) {
+ topo_mod_dprintf(mod, "failed to set FRU on %s: %s", UFM,
+ topo_strerror(err));
+ (void) topo_mod_seterrno(mod, err);
+ goto slotfail;
+ }
+
+ pgi.tpi_name = TOPO_PGROUP_SLOT;
+ pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
+ pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
+ pgi.tpi_version = TOPO_VERSION;
+ rc = topo_pgroup_create(slotnode, &pgi, &err);
+
+ if (rc == 0)
+ rc += topo_prop_set_uint32(slotnode, TOPO_PGROUP_SLOT,
+ TOPO_PROP_SLOT_TYPE, TOPO_PROP_IMMUTABLE,
+ TOPO_SLOT_TYPE_UFM, &err);
+
+ pgi.tpi_name = TOPO_PGROUP_UFM_SLOT;
+
+ if (rc == 0)
+ rc += topo_pgroup_create(slotnode, &pgi, &err);
+
+ if (rc == 0) {
+ rc += topo_prop_set_uint32(slotnode, TOPO_PGROUP_UFM_SLOT,
+ TOPO_PROP_UFM_SLOT_MODE, TOPO_PROP_IMMUTABLE,
+ slotinfo->usi_mode, &err);
+ }
+
+ if (rc == 0) {
+ rc += topo_prop_set_uint32(slotnode, TOPO_PGROUP_UFM_SLOT,
+ TOPO_PROP_UFM_SLOT_ACTIVE, TOPO_PROP_IMMUTABLE,
+ (uint32_t)slotinfo->usi_active, &err);
+ }
+
+ if (rc == 0) {
+ rc += topo_prop_set_string(slotnode, TOPO_PGROUP_UFM_SLOT,
+ TOPO_PROP_UFM_SLOT_VERSION, TOPO_PROP_IMMUTABLE,
+ slotinfo->usi_version, &err);
+ }
+
+ if (rc == 0 && slotinfo->usi_extra != NULL) {
+ nvpair_t *elem = NULL;
+ char *pname, *pval;
+
+ while ((elem = nvlist_next_nvpair(slotinfo->usi_extra,
+ elem)) != NULL) {
+ if (nvpair_type(elem) != DATA_TYPE_STRING)
+ continue;
+
+ pname = nvpair_name(elem);
+ if ((rc -= nvpair_value_string(elem, &pval)) != 0)
+ break;
+
+ rc += topo_prop_set_string(slotnode,
+ TOPO_PGROUP_UFM_SLOT, pname, TOPO_PROP_IMMUTABLE,
+ pval, &err);
+
+ if (rc != 0)
+ break;
+ }
+ }
+
+ if (rc != 0) {
+ topo_mod_dprintf(mod, "error setting properties on %s node",
+ SLOT);
+ (void) topo_mod_seterrno(mod, err);
+ goto slotfail;
+ }
+ return (slotnode);
+
+slotfail:
+ topo_node_unbind(slotnode);
+ return (NULL);
+}
+
+/*
+ * This is a convenience routine to allow enumerator modules to easily create
+ * the necessary UFM node layout for the most common case, which will be a
+ * single UFM with a single slot. This routine assumes that the caller has
+ * already created the containing range via a call to topo_node_range_create().
+ *
+ * For more complex scenarios (like multiple slots per UFM), callers can set
+ * the slotinfo param to NULL. In this case the ufm node will get created, but
+ * it will skip creating the slot node - allowing the module to manually call
+ * topo_mod_create_ufm_slot() to create custom UFM slots.
+ */
+tnode_t *
+topo_mod_create_ufm(topo_mod_t *mod, tnode_t *parent, const char *descr,
+ topo_ufm_slot_info_t *slotinfo)
+{
+ nvlist_t *auth = NULL, *fmri = NULL;
+ tnode_t *ufmnode, *slotnode;
+ topo_pgroup_info_t pgi;
+ int err, rc;
+
+ if ((auth = topo_mod_auth(mod, parent)) == NULL) {
+ topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
+ topo_mod_errmsg(mod));
+ /* errno set */
+ return (NULL);
+ }
+
+ if ((fmri = topo_mod_hcfmri(mod, parent, FM_HC_SCHEME_VERSION,
+ UFM, 0, NULL, auth, NULL, NULL, NULL)) ==
+ NULL) {
+ nvlist_free(auth);
+ topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
+ topo_mod_errmsg(mod));
+ /* errno set */
+ return (NULL);
+ }
+
+ if ((ufmnode = topo_node_bind(mod, parent, UFM, 0, fmri)) == NULL) {
+ nvlist_free(auth);
+ nvlist_free(fmri);
+ topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
+ topo_mod_errmsg(mod));
+ /* errno set */
+ return (NULL);
+ }
+
+ /* Create authority and system pgroups */
+ topo_pgroup_hcset(ufmnode, auth);
+ nvlist_free(auth);
+ nvlist_free(fmri);
+
+ /* Just inherit the parent's FRU */
+ if (topo_node_fru_set(ufmnode, NULL, 0, &err) != 0) {
+ topo_mod_dprintf(mod, "failed to set FRU on %s: %s", UFM,
+ topo_strerror(err));
+ (void) topo_mod_seterrno(mod, err);
+ goto ufmfail;
+ }
+
+ pgi.tpi_name = TOPO_PGROUP_UFM;
+ pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
+ pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
+ pgi.tpi_version = TOPO_VERSION;
+ rc = topo_pgroup_create(ufmnode, &pgi, &err);
+
+ if (rc == 0)
+ rc += topo_prop_set_string(ufmnode, TOPO_PGROUP_UFM,
+ TOPO_PROP_UFM_DESCR, TOPO_PROP_IMMUTABLE, descr, &err);
+
+ if (rc != 0) {
+ topo_mod_dprintf(mod, "error setting properties on %s node",
+ UFM);
+ (void) topo_mod_seterrno(mod, err);
+ goto ufmfail;
+ }
+
+ if (slotinfo != NULL) {
+ if (topo_node_range_create(mod, ufmnode, SLOT, 0, 0) < 0) {
+ topo_mod_dprintf(mod, "error creating %s range", SLOT);
+ goto ufmfail;
+ }
+ slotnode = topo_mod_create_ufm_slot(mod, ufmnode, slotinfo);
+
+ if (slotnode == NULL)
+ goto ufmfail;
+ }
+ return (ufmnode);
+
+ufmfail:
+ topo_node_unbind(ufmnode);
+ return (NULL);
+}
diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_mod.h b/usr/src/lib/fm/topo/libtopo/common/topo_mod.h
index 762cc9d05c..5ef516cd09 100644
--- a/usr/src/lib/fm/topo/libtopo/common/topo_mod.h
+++ b/usr/src/lib/fm/topo/libtopo/common/topo_mod.h
@@ -260,6 +260,11 @@ extern int topo_prop_method_register(tnode_t *, const char *, const char *,
topo_type_t, const char *, const nvlist_t *, int *);
extern void topo_prop_method_unregister(tnode_t *, const char *, const char *);
+extern tnode_t *topo_mod_create_ufm(topo_mod_t *, tnode_t *, const char *,
+ topo_ufm_slot_info_t *);
+extern tnode_t *topo_mod_create_ufm_slot(topo_mod_t *, tnode_t *,
+ topo_ufm_slot_info_t *);
+
/*
* This enum definition is used to define a set of error tags associated with
* the module api error conditions. The shell script mkerror.sh is
diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_mod.map b/usr/src/lib/fm/topo/libtopo/common/topo_mod.map
index 53a0177ab3..0e91797461 100644
--- a/usr/src/lib/fm/topo/libtopo/common/topo_mod.map
+++ b/usr/src/lib/fm/topo/libtopo/common/topo_mod.map
@@ -73,6 +73,9 @@ SYMBOL_SCOPE {
topo_mod_smbios { TYPE = FUNCTION; FLAGS = extern };
topo_mod_pcidb { TYPE = FUNCTION; FLAGS = extern };
+ topo_mod_create_ufm { TYPE = FUNCTION; FLAGS = extern };
+ topo_mod_create_ufm_slot { TYPE = FUNCTION; FLAGS = extern };
+
topo_method_register { TYPE = FUNCTION; FLAGS = extern };
topo_method_unregister { TYPE = FUNCTION; FLAGS = extern };
topo_method_unregister_all { TYPE = FUNCTION; FLAGS = extern };
diff --git a/usr/src/lib/fm/topo/modules/common/disk/disk_common.c b/usr/src/lib/fm/topo/modules/common/disk/disk_common.c
index da7a702662..ef4d638f94 100644
--- a/usr/src/lib/fm/topo/modules/common/disk/disk_common.c
+++ b/usr/src/lib/fm/topo/modules/common/disk/disk_common.c
@@ -304,6 +304,26 @@ disk_set_props(topo_mod_t *mod, tnode_t *parent,
}
err = 0;
+ /*
+ * Create UFM node to capture the drive firmware version
+ */
+ if (dnode->ddn_firm != NULL) {
+ topo_ufm_slot_info_t slotinfo = { 0 };
+
+ slotinfo.usi_version = dnode->ddn_firm;
+ slotinfo.usi_active = B_TRUE;
+ if (strcmp(topo_node_name(parent), USB_DEVICE) == 0)
+ slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_NONE;
+ else
+ slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_WO;
+ if (topo_node_range_create(mod, dtn, UFM, 0, 0) != 0 ||
+ topo_mod_create_ufm(mod, dtn, "drive firmware",
+ &slotinfo) == NULL) {
+ topo_mod_dprintf(mod, "failed to create %s node", UFM);
+ goto out;
+ }
+ }
+
out:
if (drive_descr != 0)
dm_free_descriptor(drive_descr);
diff --git a/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c b/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c
index 1417440b2f..b915529033 100644
--- a/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c
+++ b/usr/src/lib/fm/topo/modules/common/pcibus/pcibus.c
@@ -21,7 +21,7 @@
/*
* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2018, Joyent, Inc.
+ * Copyright 2019 Joyent, Inc.
*/
#include <sys/fm/protocol.h>
@@ -31,6 +31,8 @@
#include <string.h>
#include <strings.h>
#include <alloca.h>
+#include <fcntl.h>
+#include <unistd.h>
#include <sys/param.h>
#include <sys/pci.h>
#include <sys/pcie.h>
@@ -38,6 +40,9 @@
#include <libnvpair.h>
#include <fm/topo_mod.h>
#include <fm/topo_hc.h>
+#include <sys/ddi_ufm.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <hostbridge.h>
#include <pcibus.h>
@@ -159,6 +164,189 @@ hostbridge_asdevice(topo_mod_t *mod, tnode_t *bus)
return (0);
}
+static int
+pciexfn_add_ufm(topo_mod_t *mod, tnode_t *node)
+{
+ char *devpath = NULL;
+ ufm_ioc_getcaps_t ugc = { 0 };
+ ufm_ioc_bufsz_t ufbz = { 0 };
+ ufm_ioc_report_t ufmr = { 0 };
+ nvlist_t *ufminfo = NULL, **images;
+ uint_t nimages;
+ int err, fd, ret = -1;
+
+ if (topo_prop_get_string(node, TOPO_PGROUP_IO, TOPO_IO_DEV, &devpath,
+ &err) != 0) {
+ return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
+ }
+ if (strlen(devpath) >= MAXPATHLEN) {
+ topo_mod_dprintf(mod, "devpath is too long: %s", devpath);
+ topo_mod_strfree(mod, devpath);
+ return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
+ }
+
+ if ((fd = open(DDI_UFM_DEV, O_RDONLY)) < 0) {
+ topo_mod_dprintf(mod, "%s: failed to open %s", __func__,
+ DDI_UFM_DEV);
+ topo_mod_strfree(mod, devpath);
+ return (0);
+ }
+ /*
+ * Make an ioctl to probe if the driver for this function is
+ * UFM-capable. If the ioctl fails or if it doesn't advertise the
+ * DDI_UFM_CAP_REPORT capability, we bail out.
+ */
+ ugc.ufmg_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ugc.ufmg_devpath, devpath, MAXPATHLEN);
+ if (ioctl(fd, UFM_IOC_GETCAPS, &ugc) < 0) {
+ topo_mod_dprintf(mod, "UFM_IOC_GETCAPS failed: %s",
+ strerror(errno));
+ (void) close(fd);
+ topo_mod_strfree(mod, devpath);
+ return (0);
+ }
+ if ((ugc.ufmg_caps & DDI_UFM_CAP_REPORT) == 0) {
+ topo_mod_dprintf(mod, "driver doesn't advertise "
+ "DDI_UFM_CAP_REPORT");
+ (void) close(fd);
+ topo_mod_strfree(mod, devpath);
+ return (0);
+ }
+
+ /*
+ * If we made it this far, then the driver is indeed UFM-capable and
+ * is capable of reporting its firmware information. First step is to
+ * make an ioctl to query the size of the report data so that we can
+ * allocate a buffer large enough to hold it.
+ */
+ ufbz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ufbz.ufbz_devpath, devpath, MAXPATHLEN);
+ if (ioctl(fd, UFM_IOC_REPORTSZ, &ufbz) < 0) {
+ topo_mod_dprintf(mod, "UFM_IOC_REPORTSZ failed: %s\n",
+ strerror(errno));
+ (void) close(fd);
+ topo_mod_strfree(mod, devpath);
+ return (0);
+ }
+
+ ufmr.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ if ((ufmr.ufmr_buf = topo_mod_alloc(mod, ufbz.ufbz_size)) == NULL) {
+ topo_mod_dprintf(mod, "failed to alloc %u bytes\n",
+ ufbz.ufbz_size);
+ (void) close(fd);
+ topo_mod_strfree(mod, devpath);
+ return (topo_mod_seterrno(mod, EMOD_NOMEM));
+ }
+ ufmr.ufmr_bufsz = ufbz.ufbz_size;
+ (void) strlcpy(ufmr.ufmr_devpath, devpath, MAXPATHLEN);
+ topo_mod_strfree(mod, devpath);
+
+ /*
+ * Now, make the ioctl to retrieve the actual report data. The data
+ * is stored as a packed nvlist.
+ */
+ if (ioctl(fd, UFM_IOC_REPORT, &ufmr) < 0) {
+ topo_mod_dprintf(mod, "UFM_IOC_REPORT failed: %s\n",
+ strerror(errno));
+ topo_mod_free(mod, ufmr.ufmr_buf, ufmr.ufmr_bufsz);
+ (void) close(fd);
+ return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
+ }
+ (void) close(fd);
+
+ if (nvlist_unpack(ufmr.ufmr_buf, ufmr.ufmr_bufsz, &ufminfo,
+ NV_ENCODE_NATIVE) != 0) {
+ topo_mod_dprintf(mod, "failed to unpack nvlist\n");
+ topo_mod_free(mod, ufmr.ufmr_buf, ufmr.ufmr_bufsz);
+ return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
+ }
+ topo_mod_free(mod, ufmr.ufmr_buf, ufmr.ufmr_bufsz);
+
+ if (nvlist_lookup_nvlist_array(ufminfo, DDI_UFM_NV_IMAGES, &images,
+ &nimages) != 0) {
+ topo_mod_dprintf(mod, "failed to lookup %s nvpair",
+ DDI_UFM_NV_IMAGES);
+ (void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
+ goto err;
+ }
+ if (topo_node_range_create(mod, node, UFM, 0, (nimages - 1)) != 0) {
+ topo_mod_dprintf(mod, "failed to create %s range", UFM);
+ /* errno set */
+ goto err;
+ }
+ for (uint_t i = 0; i < nimages; i++) {
+ tnode_t *ufmnode = NULL;
+ char *descr;
+ uint_t nslots;
+ nvlist_t **slots;
+
+ if (nvlist_lookup_string(images[i], DDI_UFM_NV_IMAGE_DESC,
+ &descr) != 0 ||
+ nvlist_lookup_nvlist_array(images[i],
+ DDI_UFM_NV_IMAGE_SLOTS, &slots, &nslots) != 0) {
+ (void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
+ goto err;
+ }
+ if ((ufmnode = topo_mod_create_ufm(mod, node, descr, NULL)) ==
+ NULL) {
+ topo_mod_dprintf(mod, "failed to create ufm nodes for "
+ "%s", descr);
+ /* errno set */
+ goto err;
+ }
+ for (uint_t s = 0; s < nslots; s++) {
+ topo_ufm_slot_info_t slotinfo = { 0 };
+ uint32_t slotattrs;
+
+ if (nvlist_lookup_string(slots[s],
+ DDI_UFM_NV_SLOT_VERSION,
+ (char **)&slotinfo.usi_version) != 0 ||
+ nvlist_lookup_uint32(slots[s],
+ DDI_UFM_NV_SLOT_ATTR, &slotattrs) != 0) {
+ topo_node_unbind(ufmnode);
+ topo_mod_dprintf(mod, "malformed slot nvlist");
+ (void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
+ goto err;
+ }
+ (void) nvlist_lookup_nvlist(slots[s],
+ DDI_UFM_NV_SLOT_MISC, &slotinfo.usi_extra);
+
+ if (slotattrs & DDI_UFM_ATTR_READABLE &&
+ slotattrs & DDI_UFM_ATTR_WRITEABLE)
+ slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_RW;
+ else if (slotattrs & DDI_UFM_ATTR_READABLE)
+ slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_RO;
+ else if (slotattrs & DDI_UFM_ATTR_WRITEABLE)
+ slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_WO;
+ else
+ slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_NONE;
+
+ if (slotattrs & DDI_UFM_ATTR_ACTIVE)
+ slotinfo.usi_active = B_TRUE;
+
+ if (topo_node_range_create(mod, ufmnode, SLOT, 0,
+ (nslots - 1)) < 0) {
+ topo_mod_dprintf(mod, "failed to create %s "
+ "range", SLOT);
+ /* errno set */
+ goto err;
+ }
+ if (topo_mod_create_ufm_slot(mod, ufmnode,
+ &slotinfo) == NULL) {
+ topo_node_unbind(ufmnode);
+ topo_mod_dprintf(mod, "failed to create ufm "
+ "slot %d for %s", s, descr);
+ /* errno set */
+ goto err;
+ }
+ }
+ }
+ ret = 0;
+err:
+ nvlist_free(ufminfo);
+ return (ret);
+}
+
tnode_t *
pciexfn_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
topo_instance_t i)
@@ -235,6 +423,17 @@ pciexfn_declare(topo_mod_t *mod, tnode_t *parent, di_node_t dn,
topo_node_unbind(ntn);
return (NULL);
}
+
+ /*
+ * Check if the driver associated with this function exports firmware
+ * information via the DDI UFM subsystem and, if so, create the
+ * corresponding ufm topo nodes.
+ */
+ if (pciexfn_add_ufm(mod, ntn) != 0) {
+ topo_node_unbind(ntn);
+ return (NULL);
+ }
+
/*
* We may find pci-express buses or plain-pci buses beneath a function
*/
diff --git a/usr/src/lib/fm/topo/modules/common/smbios/smbios_enum.c b/usr/src/lib/fm/topo/modules/common/smbios/smbios_enum.c
index a66396e170..58cf7b47f5 100644
--- a/usr/src/lib/fm/topo/modules/common/smbios/smbios_enum.c
+++ b/usr/src/lib/fm/topo/modules/common/smbios/smbios_enum.c
@@ -520,18 +520,6 @@ smbios_enum_motherboard(smbios_hdl_t *shp, smb_enum_data_t *smed)
if (rc == 0 && asset != NULL)
rc += topo_prop_set_string(mbnode, TOPO_PGROUP_MOTHERBOARD,
TOPO_PROP_MB_ASSET, TOPO_PROP_IMMUTABLE, asset, &err);
- if (rc == 0 && bios_vendor != NULL)
- rc += topo_prop_set_string(mbnode, TOPO_PGROUP_MOTHERBOARD,
- TOPO_PROP_MB_FIRMWARE_VENDOR, TOPO_PROP_IMMUTABLE,
- bios_vendor, &err);
- if (rc == 0 && bios_rev != NULL)
- rc += topo_prop_set_string(mbnode, TOPO_PGROUP_MOTHERBOARD,
- TOPO_PROP_MB_FIRMWARE_REV, TOPO_PROP_IMMUTABLE,
- bios_rev, &err);
- if (rc == 0 && bios_reldate != NULL)
- rc += topo_prop_set_string(mbnode, TOPO_PGROUP_MOTHERBOARD,
- TOPO_PROP_MB_FIRMWARE_RELDATE, TOPO_PROP_IMMUTABLE,
- bios_reldate, &err);
if (rc != 0) {
topo_mod_dprintf(mod, "error setting properties on %s node",
@@ -539,6 +527,45 @@ smbios_enum_motherboard(smbios_hdl_t *shp, smb_enum_data_t *smed)
(void) topo_mod_seterrno(mod, err);
goto err;
}
+ /*
+ * If we were able to gleen the BIOS version from SMBIOS, then set
+ * up a UFM node to capture that information.
+ */
+ if (bios_rev != NULL) {
+ topo_ufm_slot_info_t slotinfo = { 0 };
+ nvlist_t *extra;
+
+ slotinfo.usi_version = bios_rev;
+ slotinfo.usi_active = B_TRUE;
+ slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_NONE;
+
+ if (bios_vendor != NULL || bios_reldate != NULL) {
+ if (nvlist_alloc(&extra, NV_UNIQUE_NAME, 0) != 0) {
+ goto err;
+ }
+ if (bios_vendor != NULL && nvlist_add_string(extra,
+ TOPO_PROP_MB_FIRMWARE_VENDOR, bios_vendor) != 0) {
+ nvlist_free(extra);
+ goto err;
+ }
+ if (bios_reldate != NULL && nvlist_add_string(extra,
+ TOPO_PROP_MB_FIRMWARE_RELDATE, bios_reldate) !=
+ 0) {
+ nvlist_free(extra);
+ goto err;
+ }
+ slotinfo.usi_extra = extra;
+ }
+ if (topo_node_range_create(mod, mbnode, UFM, 0, 0) != 0) {
+ topo_mod_dprintf(mod, "failed to create %s range",
+ UFM);
+ nvlist_free(extra);
+ goto err;
+ }
+ (void) topo_mod_create_ufm(mod, mbnode, "BIOS", &slotinfo);
+ nvlist_free(extra);
+ }
+
err:
topo_mod_strfree(mod, manuf);
topo_mod_strfree(mod, prod);
diff --git a/usr/src/man/man7d/Makefile b/usr/src/man/man7d/Makefile
index 3f0523b84a..8522deb37b 100644
--- a/usr/src/man/man7d/Makefile
+++ b/usr/src/man/man7d/Makefile
@@ -126,6 +126,7 @@ _MANFILES= aac.7d \
tty.7d \
ttymux.7d \
tzmon.7d \
+ ufm.7d \
ugen.7d \
uhci.7d \
usb_ac.7d \
diff --git a/usr/src/man/man7d/ufm.7d b/usr/src/man/man7d/ufm.7d
new file mode 100644
index 0000000000..5cd2b3581e
--- /dev/null
+++ b/usr/src/man/man7d/ufm.7d
@@ -0,0 +1,206 @@
+.\"
+.\" 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 2019 Joyent, Inc.
+.\"
+.Dd Jun 5, 2019
+.Dt UFM 7D
+.Os
+.Sh NAME
+.Nm ufm
+.Nd Upgradeable Firmware Module driver
+.Sh SYNOPSIS
+.Pa /dev/ufm
+.Lp
+.In sys/ddi_ufm.h
+.Sh DESCRIPTION
+The
+.Nm
+device is a character special file that provides acccess to
+Upgradeable Firmware Image information, as described in
+.Xr ddi_ufm 9E
+via a private ioctl interface.
+.Sh FILES
+.Bl -tag -width Pa
+.It Pa /kernel/drv/amd64/ufm
+64-bit AMD64 ELF kernel driver
+.It Pa /kernel/drv/sparcv9/ufm
+64-bit SPARC ELF kernel driver
+.El
+.Sh IOCTLS
+The
+.Nm
+driver implements a versioned ioctl interface for accessing UFM facilities.
+The ioctl interfaces are defined in sys/ddi_ufm.h.
+The following ioctl cmds are supported by DDI_UFM_VERSION_ONE:
+.Bl -tag -width Dv
+.It Dv UFM_IOC_GETCAPS
+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_t;
+.Ed
+.Pp
+The ufm_ioc_getcaps_t type 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.
+.Bd -literal
+typedef struct ufm_ioc_getcaps {
+ uint_t ufmg_version; /* DDI_UFM_VERSION */
+ uint_t ufmg_caps; /* UFM Caps */
+ char ufmg_devpath[MAXPATHLEN];
+} ufm_ioc_getcaps_t;
+.Ed
+.It UFM_IOC_REPORTSZ
+The
+.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.
+.Bd -literal
+typedef struct ufm_ioc_bufsz {
+ uint_t ufbz_version; /* DDI_UFM_VERSION */
+ 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
+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.
+Callers should specify the ufmr_version, ufmr_bufsz and ufmr_devpath fields.
+On success, the ufmr_buf field will point to a packed nvlist containing the UFM
+data for the specified device instance.
+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 */
+ 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;
+.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
+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
+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,
+the ioctl will fail with errno set to
+.Er EOVERFLOW .
+.Ed
+.El
+.Sh EXAMPLES
+This example demonstrates how to use the UFM_IOC_GETCAPS ioctl to determine
+the UFM capabilities of a given device instance.
+.Bd -literal
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ddi_ufm.h>
+#include <sys/types.h>
+
+static const char devname[] = "/pci@ce,0/pci8086,2030@0/pci15d9,808@0";
+
+int
+main(int argc, char **argv)
+{
+ int fd;
+ ufm_ioc_getcaps_t ioc = { 0 };
+
+ if ((fd = open(DDI_UFM_DEV, O_RDWR)) < 0) {
+ (void) fprintf(stderr, "failed to open %s (%s)\n", DDI_UFM_DEV,
+ strerror(errno));
+ return (1);
+ }
+
+ ioc.ufmg_version = DDI_UFM_CURRENT_VERSION;
+ (void) strcpy(ioc.ufmg_devpath, devname);
+
+ if (ioctl(fd, UFM_IOC_GETCAPS, &ioc) < 0) {
+ (void) fprintf(stderr, "getcaps ioctl failed (%s)\n",
+ strerror(errno));
+ (void) close(fd);
+ return (1);
+ }
+ if ((ioc.ufmg_caps & DDI_UFM_CAP_REPORT) == 0) {
+ (void) printf("Driver does not support DDI_UFM_CAP_REPORT\n");
+ } else {
+ (void) printf("Driver supports DDI_UFM_CAP_REPORT\n");
+ }
+ (void) close(fd);
+ return (0);
+}
+.Ed
+.Sh ERRORS
+On failure to open or perform ioctls to the
+.Nm
+driver,
+.Va errno
+will be set to indicate the type of error.
+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 EIO
+A failure occurred while executing a DDI UFM entry point.
+.El
+.Sh INTERFACE STABILITY
+.Sy Evolving
+.Sh SEE ALSO
+.Xr ddi_ufm 9E ,
+.Xr ddi_ufm_image 9E ,
+.Xr ddi_ufm_slot 9E ,
+.Xr ddi_ufm 9F
diff --git a/usr/src/man/man9e/Makefile b/usr/src/man/man9e/Makefile
index 8036f34cda..a67785e520 100644
--- a/usr/src/man/man9e/Makefile
+++ b/usr/src/man/man9e/Makefile
@@ -12,7 +12,7 @@
#
# Copyright 2011, Richard Lowe
# Copyright 2013 Nexenta Systems, Inc. All rights reserved.
-# Copyright (c) 2017, Joyent, Inc.
+# Copyright (c) 2019, Joyent, Inc.
#
include $(SRC)/Makefile.master
@@ -28,6 +28,7 @@ MANFILES= Intro.9e \
close.9e \
csx_event_handler.9e \
detach.9e \
+ ddi_ufm.9e \
devmap.9e \
devmap_access.9e \
devmap_contextmgt.9e \
@@ -96,7 +97,11 @@ MANFILES= Intro.9e \
usba_hcdi.9e \
write.9e
-MANLINKS= _info.9e \
+MANLINKS= ddi_ufm_op_fill_image.9e \
+ ddi_ufm_op_fill_slot.9e \
+ ddi_ufm_op_getcaps.9e \
+ ddi_ufm_op_nimages.9e \
+ _info.9e \
_init.9e \
gldv3.9e \
GLDv3.9e \
@@ -130,6 +135,10 @@ MANLINKS= _info.9e \
usba_hcdi_pipe_close.9e \
usba_hcdi_pipe_stop_isoc_polling.9e
+ddi_ufm_op_fill_image.9e := LINKSRC = ddi_ufm.9e
+ddi_ufm_op_fill_slot.9e := LINKSRC = ddi_ufm.9e
+ddi_ufm_op_getcaps.9e := LINKSRC = ddi_ufm.9e
+ddi_ufm_op_nimages.9e := LINKSRC = ddi_ufm.9e
intro.9e := LINKSRC = Intro.9e
_info.9e := LINKSRC = _fini.9e
diff --git a/usr/src/man/man9e/ddi_ufm.9e b/usr/src/man/man9e/ddi_ufm.9e
new file mode 100644
index 0000000000..d0602350d2
--- /dev/null
+++ b/usr/src/man/man9e/ddi_ufm.9e
@@ -0,0 +1,421 @@
+.\"
+.\" 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 2019 Joyent, Inc.
+.\"
+.Dd Apr 30, 2019
+.Dt DDI_UFM 9E
+.Os
+.Sh NAME
+.Nm ddi_ufm ,
+.Nm ddi_ufm_op_nimages ,
+.Nm ddi_ufm_op_fill_image ,
+.Nm ddi_ufm_op_fill_slot ,
+.Nm ddi_ufm_op_getcaps
+.Nd DDI upgradable firmware module entry points
+.Sh SYNOPSIS
+.Vt typedef struct ddi_ufm_handle ddi_ufm_handle_t
+.Vt typedef struct ddi_ufm_ops ddi_ufm_ops_t
+.In sys/ddi_ufm.h
+.Ft int
+.Fo ddi_ufm_op_getcaps
+.Fa "ddi_ufm_handle_t *uhp"
+.Fa "void *drv_arg"
+.Fa "ddi_ufm_cap_t *caps"
+.Fc
+.Ft int
+.Fo ddi_ufm_op_nimages
+.Fa "ddi_ufm_handle_t *uhp"
+.Fa "void *drv_arg"
+.Fa "uint_t *nimgp"
+.Fc
+.Ft int
+.Fo ddi_ufm_op_fill_image
+.Fa "ddi_ufm_handle_t *uhp"
+.Fa "void *drv_arg"
+.Fa "uint_t imgid"
+.Fa "ddi_ufm_image_t *uip"
+.Fc
+.Ft int
+.Fo ddi_ufm_op_fill_slot
+.Fa "ddi_ufm_handle_t *uhp"
+.Fa "void *drv_arg"
+.Fa "uint_t imgid"
+.Fa "uint_t slotid"
+.Fa "ddi_ufm_slot_t *usp"
+.Fc
+.Sh INTERFACE LEVEL
+.Sy Evolving - This interface is evolving still in illumos. API and ABI stability is not guaranteed.
+.Sh PARAMETERS
+.Bl -tag -width Fa
+.It Fa uhp
+A handle corresponding to the device's UFM handle.
+This is the same value as returned in
+.Xr ddi_ufm_init 9F .
+.It Fa drv_arg
+This is a private value that the driver passed in when calling
+.Xr ddi_ufm_init 9F .
+.It Fa nimgp
+A pointer that the driver should set with a number of images.
+.It Fa nslotp
+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
+An opaque pointer that represents a UFM image.
+.It Fa slotid
+An integer indicating which slot information is being requested for.
+.It Fa usp
+An opaque pointer that represents a UFM slot.
+.El
+.Sh DESCRIPTION
+Upgradable firmware modules (UFM) are a potential component of many
+devices.
+These interfaces aim to provide a simple series of callbacks
+for a device driver to implement such that it is easy to report
+information and in the future, manipulate firmware modules.
+.Ss UFM Background
+UFMs may come in different flavors and styles ranging from a
+firmware blob, to an EEPROM image, to microcode, and more.
+Take for example a hard drive.
+While it is a field replaceable unit (FRU), it also contains some amount
+of firmware that manages the drive which can be updated independently of
+replacing the drive.
+.Pp
+The motherboard often has a UFM in the form of the BIOS or UEFI.
+The Lights out management controller on a system has a UFM, which is usually
+the entire system image.
+CPUs also have a UFM in the form of microcode.
+.Pp
+An important property of a UFM is that it is a property of the device
+itself.
+For example, many WiFi device drivers are required to send a binary blob of
+firmware to the device after every reset.
+Because these images are not properties of the device and must be upgraded by
+either changing the device driver or related system files, we do not consider
+these UFMs.
+.Pp
+There are also devices that have firmware which is a property of the
+device, but may not be upgradable from the running OS.
+This may be because the vendor doesn't have tooling to upgrade the image or
+because the firmware image itself cannot be upgraded in the field at all.
+For example, a YubiKey has a firmware image that's burned into it in the
+factory, but there is no way to change the firmware on it short of
+replacing the device in its entirety.
+However, because these images are a permanent part of the device, we also
+consider them a UFM.
+.Ss Images and Slots
+A device that supports UFMs is made up of one or more distinct firmware
+images.
+Each image has its own unique purpose.
+For example, a motherboard may have both a BIOS and a CPLD image, each of which
+has independent firmware revisions.
+.Pp
+A given image may have a number of slots.
+A slot represents a particular version of the image.
+Only one slot can be active at a given time.
+Devices support slots such that a firmware image can be downloaded
+to the device without impacting the current device if it fails half-way
+through.
+The slot that's currently in use is referred to as the
+.Em active
+slot.
+.Pp
+The various entry points are designed such that all a driver has to do
+is provide information about the image and its slots to the kernel, it
+does not have to wrangle with how that is marshalled to users and the
+appearance of those structures.
+.Ss Registering with the UFM Subsystem
+During a device driver's
+.Xr attach 9E
+entry point, a device driver should register with the UFM subsystem by
+filling out a UFM operations vector and then calling
+.Xr ddi_ufm_init 9F .
+The driver may pass in a value, usually a pointer to its soft state
+pointer, which it will then receive when its subsequent entry points are
+called.
+.Pp
+Once the driver has finished initializing, it must call
+.Xr ddi_ufm_update 9F
+to indicate that the driver is in a state where it's ready to receive
+calls to the entry points.
+.Pp
+The various UFM entry points may be called from an arbitrary kernel
+context.
+However, they will only ever be called from a single thread at
+a given time.
+.Ss UFM operations vector
+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,
+ 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 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,
+ ddi_ufm_cap_t *caps);
+} ddi_ufm_ops_t;
+.Ed
+.Pp
+The
+.Fn ddi_ufm_op_nimages
+entry point is optional.
+If a device only has a single image, then there is no reason to implement the
+.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.
+If a driver indicates support for multiple images or slots then the images
+or slots will be numbered sequentially going from 0 to the number of images or
+slots minus one.
+These values will be passed to the various entry points to indicate which image
+and slot the system is interested in.
+It is up to the driver to maintain a consistent view of the images and slots
+for a given UFM.
+.Pp
+The members of this structure should be filled in the following ways:
+.Bl -tag -width Fn
+.It Fn ddi_ufm_op_nimages
+The
+.Fn ddi_ufm_op_nimages
+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.
+.Pp
+It is legal for a device to pass in zero for this value, which indicates
+that there are none present.
+.Pp
+Upon successful completion, 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 EIO
+An error occurred while communicating with the device to determine the
+number of firmware images.
+.El
+.It Fn ddi_ufm_op_fill_image
+The
+.Fn ddi_ufm_op_fill_image
+entry point is used to fill in information about a given image.
+The value in
+.Fa imgid
+is used to indicate which image the system is asking to fill
+information about.
+If the driver does not recognize the image ID in
+.Fa imgid
+then it should return an error.
+.Pp
+The
+.Ft ddi_ufm_image_t
+structure passed in
+.Fa uip
+is opaque.
+To fill in information about the image, the driver should call the functions
+described in
+.Xr ddi_ufm_image 9F .
+.Pp
+The driver should call the
+.Xr ddi_ufm_image_set_desc 9F
+function to set a description of the image which indicates its purpose.
+This should be a human-readable string.
+The driver may also set any ancillary data that it deems may be useful with the
+.Xr ddi_ufm_image_set_misc 9F function.
+This function takes an nvlist, allowing the driver to set arbitrary keys and values.
+.Pp
+Once the driver has finished setting all of the information about the
+image then 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 indicated by
+.Fa imgid
+is unknown.
+.It Er EIO
+An error occurred talking to the device while trying to fill out
+firmware image information.
+.It Er ENOMEM
+The driver was unable to allocate memory while filling out image
+information.
+.El
+.It Fn ddi_ufm_op_fill_slot
+The
+.Fn ddi_ufm_op_fill_slot
+function is used to fill in information about a specific slot for a
+specific image.
+The value in
+.Fa imgid
+indicates the image the system wants slot information for and the value
+in
+.Fa slotid
+indicates which slot of that image the system is interested in.
+If the device driver does not recognize the value in either or
+.Fa imgid
+or
+.Fa slotid ,
+then it should return an error.
+.Pp
+The
+.Ft ddi_ufm_slot_t
+structure passed in
+.Fa usp
+is opaque.
+To fill in information about the image the driver should call the functions
+described in
+.Xr ddi_ufm_slot 9F .
+.Pp
+The driver should call the
+.Xr ddi_ufm_slot_set_version 9F
+function to indicate the version of the UFM.
+The version is a device-specific character string.
+It should contain the current version of the UFM as a human can understand it
+and it should try to match the format used by device vendor.
+.Pp
+The
+.Xr ddi_ufm_slot_set_attrs 9F
+function should be used to set the attributes of the UFM slot.
+These attributes include the following enumeration values:
+.Bl -tag -width Dv
+.It Dv DDI_UFM_ATTR_READABLE
+This attribute indicates that the firmware image in the specified slot
+may be read, even if the device driver does not currently support such
+functionality.
+.It Dv DDI_UFM_ATTR_WRITEABLE
+This attributes indicates that the firmware image in the specified slot
+may be updated, even if the driver does not currently support such
+functionality.
+.It Dv DDI_UFM_ATTR_ACTIVE
+This attributes indicates that the firmware image in the specified slot
+is the active
+.Pq i.e. currently running
+firmware.
+Only one slot should be marked active.
+.It Dv DDI_UFM_ATTR_EMPTY
+This attributes indicates that the specified slot does not currently contain
+any firmware image.
+.El
+.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
+function.
+.Pp
+Once the driver has finished setting all of the information about the
+slot then 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 indicated by
+.Fa imgid
+and
+.Fa slotid
+is unknown.
+.It Er EIO
+An error occurred talking to the device while trying to fill out
+firmware slot information.
+.It Er ENOMEM
+The driver was unable to allocate memory while filling out slot
+information.
+.El
+.It Fn ddi_ufm_op_getcaps
+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.
+.Pp
+The driver should indicate the supported capabilities by setting the value in
+the
+.Ft caps
+parameter.
+Once the driver has populated
+.Ft caps
+with an appropriate value, then 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 EIO
+An error occurred talking to the device while trying to discover firmware
+capabilties.
+.It Er ENOMEM
+The driver was unable to allocate memory.
+.El
+.El
+.Ss Caching and Updates
+The system will fetch firmware and slot information on an as-needed
+basis.
+Once it obtains some information, it may end up caching this information on
+behalf of the driver.
+Whenever the driver believes that something could have changed -- it need know
+that it has -- then the driver must call
+.Xr ddi_ufm_update 9F .
+.Ss Locking
+All UFM operations on a single UFM handle will always be run serially.
+However, the device driver may still need to apply adequate locking to
+its structure members as other may be accessing the same data structure
+or trying to communicate with the device.
+.Ss Unregistering from the UFM subsystem
+When a device driver is detached, it should unregister from the UFM
+subsystem.
+To do so, the driver should call
+.Xr ddi_ufm_fini 9F .
+By the time this function returns, the driver is guaranteed that no UFM
+entry points will be called.
+However, if there are outstanding UFM related activity, the function will
+block until it is terminated.
+.Ss ioctl Interface
+Userland consumers can access UFM information via a set of ioctls that are
+implemented by the
+.Xr ufm 7D
+driver.
+.Sh CONTEXT
+The various UFM entry points that a device driver must implement will
+always be called from
+.Sy kernel
+context.
+.Sh SEE ALSO
+.Xr Intro 2 ,
+.Xr ufd 7D ,
+.Xr attach 9E ,
+.Xr ddi_ufm_fini 9F ,
+.Xr ddi_ufm_image 9F ,
+.Xr ddi_ufm_image_set_desc 9F ,
+.Xr ddi_ufm_image_set_misc 9F ,
+.Xr ddi_ufm_image_set_nslots 9F ,
+.Xr ddi_ufm_init 9F ,
+.Xr ddi_ufm_slot 9F ,
+.Xr ddi_ufm_slot_set_attrs 9F ,
+.Xr ddi_ufm_slot_set_misc 9F ,
+.Xr ddi_ufm_slot_set_version 9F ,
+.Xr ddi_ufm_update 9F
diff --git a/usr/src/man/man9f/Makefile b/usr/src/man/man9f/Makefile
index 578fead325..825dab0e0c 100644
--- a/usr/src/man/man9f/Makefile
+++ b/usr/src/man/man9f/Makefile
@@ -12,14 +12,14 @@
#
# Copyright 2017, Richard Lowe
# Copyright 2014 Garrett D'Amore <garrett@damore>
-# Copyright (c) 2017, Joyent, Inc.
+# Copyright 2019 Joyent, Inc.
# Copyright 2016 Nexenta Systems, Inc.
# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
include $(SRC)/Makefile.master
-MANSECT= 9f
+MANSECT= 9f
MANFILES= ASSERT.9f \
Intro.9f \
@@ -242,6 +242,9 @@ MANFILES= ASSERT.9f \
ddi_strtol.9f \
ddi_strtoll.9f \
ddi_strtoul.9f \
+ ddi_ufm.9f \
+ ddi_ufm_image.9f \
+ ddi_ufm_slot.9f \
ddi_umem_alloc.9f \
ddi_umem_iosetup.9f \
ddi_umem_lock.9f \
@@ -913,6 +916,15 @@ MANLINKS= AVL_NEXT.9f \
ddi_taskq_suspend.9f \
ddi_taskq_wait.9f \
ddi_trigger_softintr.9f \
+ ddi_ufm_fini.9f \
+ ddi_ufm_image_set_desc.9f \
+ ddi_ufm_image_set_misc.9f \
+ ddi_ufm_image_set_nslots.9f \
+ ddi_ufm_init.9f \
+ ddi_ufm_slot_set_attrs.9f \
+ ddi_ufm_slot_set_misc.9f \
+ ddi_ufm_slot_set_version.9f \
+ ddi_ufm_update.9f \
ddi_umem_free.9f \
ddi_umem_unlock.9f \
ddi_unmap_regs.9f \
@@ -1197,13 +1209,13 @@ MANLINKS= AVL_NEXT.9f \
scsi_hba_iportmap_iport_add.9f \
scsi_hba_iportmap_iport_remove.9f \
scsi_hba_pkt_free.9f \
- scsi_hba_tgtmap_destroy.9f \
- scsi_hba_tgtmap_set_begin.9f \
- scsi_hba_tgtmap_set_add.9f \
- scsi_hba_tgtmap_set_end.9f \
- scsi_hba_tgtmap_set_flush.9f \
- scsi_hba_tgtmap_tgt_add.9f \
- scsi_hba_tgtmap_tgt_remove.9f \
+ scsi_hba_tgtmap_destroy.9f \
+ scsi_hba_tgtmap_set_begin.9f \
+ scsi_hba_tgtmap_set_add.9f \
+ scsi_hba_tgtmap_set_end.9f \
+ scsi_hba_tgtmap_set_flush.9f \
+ scsi_hba_tgtmap_tgt_add.9f \
+ scsi_hba_tgtmap_tgt_remove.9f \
scsi_hba_tran_free.9f \
scsi_ifsetcap.9f \
scsi_mname.9f \
@@ -1437,7 +1449,7 @@ avl_add.9f := LINKSRC = avl.9f
avl_create.9f := LINKSRC = avl.9f
avl_destroy.9f := LINKSRC = avl.9f
avl_destroy_nodes.9f := LINKSRC = avl.9f
-avl_find.9f := LINKSRC = avl.9f
+avl_find.9f := LINKSRC = avl.9f
avl_first.9f := LINKSRC = avl.9f
avl_insert.9f := LINKSRC = avl.9f
avl_insert_here.9f := LINKSRC = avl.9f
@@ -1751,6 +1763,16 @@ ddi_devmap_segmap.9f := LINKSRC = devmap_setup.9f
devmap_load.9f := LINKSRC = devmap_unload.9f
+ddi_ufm_fini.9f := LINKSRC = ddi_ufm.9f
+ddi_ufm_image_set_desc.9f := LINKSRC = ddi_ufm_image.9f
+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_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
+
dlerrorack.9f := LINKSRC = dlbindack.9f
dlokack.9f := LINKSRC = dlbindack.9f
dlphysaddrack.9f := LINKSRC = dlbindack.9f
@@ -2095,8 +2117,8 @@ scsi_hba_iport_find.9f := LINKSRC = scsi_hba_iport_exist.9f
scsi_hba_pkt_free.9f := LINKSRC = scsi_hba_pkt_alloc.9f
-scsi_hba_tgtmap_destroy.9f := LINKSRC = scsi_hba_tgtmap_create.9f
-scsi_hba_tgtmap_set_begin.9f := LINKSRC = scsi_hba_tgtmap_create.9f
+scsi_hba_tgtmap_destroy.9f := LINKSRC = scsi_hba_tgtmap_create.9f
+scsi_hba_tgtmap_set_begin.9f := LINKSRC = scsi_hba_tgtmap_create.9f
scsi_hba_tgtmap_set_add.9f := LINKSRC = scsi_hba_tgtmap_create.9f
scsi_hba_tgtmap_set_end.9f := LINKSRC = scsi_hba_tgtmap_create.9f
scsi_hba_tgtmap_set_flush.9f := LINKSRC = scsi_hba_tgtmap_create.9f
diff --git a/usr/src/man/man9f/ddi_ufm.9f b/usr/src/man/man9f/ddi_ufm.9f
new file mode 100644
index 0000000000..3cb48ead7d
--- /dev/null
+++ b/usr/src/man/man9f/ddi_ufm.9f
@@ -0,0 +1,162 @@
+.\"
+.\" 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 (c) 2019, Joyent, Inc.
+.\"
+.Dd Apr 30, 2019
+.Dt DDI_UFM 9F
+.Os
+.Sh NAME
+.Nm ddi_ufm ,
+.Nm ddi_ufm_init ,
+.Nm ddi_ufm_update ,
+.Nm ddi_ufm_fini
+.Nd DDI upgradable firmware module interfaces
+.Sh SYNOPSIS
+.In sys/ddi_ufm.h
+.Ft int
+.Fo ddi_ufm_init
+.Fa "dev_info_t *dip"
+.Fa "int version"
+.Fa "ddi_ufm_ops_t *ops"
+.Fa "ddi_ufm_handle_t **ufmpp"
+.Fa "void *drv_arg"
+.Fc
+.Ft void
+.Fo ddi_ufm_update
+.Fa "ddi_ufm_handle_t *ufmp"
+.Fc
+.Ft void
+.Fo ddi_ufm_fini
+.Fa "ddi_ufm_handle_t *ufmp"
+.Fc
+.Sh INTERFACE LEVEL
+.Sy Evolving -
+This interface is evolving still in illumos.
+API and ABI stability is not guaranteed.
+.Sh PARAMETERS
+.Bl -tag -width Fa
+.It Fa dip
+Pointer to the devices
+.Vt dev_info
+structure for the specific instance.
+.It Fa version
+A value which indicates the current revision of the interface that the
+device supports.
+Should generally be set to
+.Dv DDI_UFM_CURRENT_VERSION .
+.It Fa ops
+A pointer to a UFM operations structure.
+See
+.Xr ddi_ufm 9E
+for more information.
+.It Fa ufmp
+A pinter to the opaque handle returned from
+.Fn ddi_ufm_init .
+.It Fa ufmpp
+A pointer to store the opaque handle from
+.Fn ddi_ufm_init .
+.It Fa drv_arg
+A driver specific argument that will be passed to various operations.
+.El
+.Sh DESCRIPTION
+These functions provide support for initializing and performing various
+upgradeable firmware module (UFM) operations.
+For more information, please see
+.Xr ddi_ufm 9E .
+.Pp
+The
+.Fn ddi_ufm_init
+function is used to initialize support for the UFM subsystem for a given
+device.
+The
+.Fa dip
+argument should be the
+.Vt dev_info
+structure of the specific device.
+The
+.Fa version
+argument represents the current revision of the UFM interface that the
+driver supports.
+Drivers inside of illumos should always use
+.Dv DDI_UFM_CURRENT_VERSION .
+Device drivers which need to bind to a specific revision, should instead
+pass the latest version:
+.Dv DDI_UFM_VERSION_ONE .
+The operations structure,
+.Fa ops ,
+should be filled according to the rules in
+.Xr ddi_ufm 9E .
+These will be the entry points that device drivers call.
+The value of
+.Fa drv_arg
+will be passed to all of the driver's entry points.
+When the function returns,
+.Fa ufmpp
+will be filled in with a handle that the driver should reference when
+needing to perform subsequent UFM operations.
+No UFM entry points will be called until after the driver calls the
+.Fn ddi_ufm_update
+function.
+.Pp
+When the device driver is detaching or needs to unregister from the UFM
+subsystem, then the device driver should call the
+.Fn ddi_ufm_fini
+function with the handle that they obtained during the call to
+initialize.
+Note, this function will block and ensure that any outstanding UFM operations
+are terminated.
+The driver must not hold any locks that are required in its callbacks across
+the call to
+.Fn ddi_ufm_fini .
+.Pp
+The
+.Fn ddi_ufm_update
+function should be used in two different circumstances.
+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.
+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
+will not have interrupts for.
+.Sh RETURN VALUES
+Upon successful completion, the
+.Fn ddi_ufm_init
+function returns zero, indicating that it has successfully registered
+with the UFM subsystem.
+.Fa ufmpp
+will be filled in with a pointer to the UFM handle.
+.Pp
+The
+.Fn ddi_ufm_init
+and
+.Fn ddi_ufm_fini
+functions are generally called from a device's
+.Xr attach 9E
+and
+.Xr fini 9E
+routines, though they may be called from
+.Sy user
+or
+.Sy kernel
+context.
+.Pp
+The
+.Fn ddi_ufm_update
+function may be called from any context except a high-level interrupt
+handler above lock level.
+.Sh SEE ALSO
+.Xr attach 9E ,
+.Xr ddi_ufm 9E ,
+.Xr fini 9E
diff --git a/usr/src/man/man9f/ddi_ufm_image.9f b/usr/src/man/man9f/ddi_ufm_image.9f
new file mode 100644
index 0000000000..606103bb8c
--- /dev/null
+++ b/usr/src/man/man9f/ddi_ufm_image.9f
@@ -0,0 +1,102 @@
+.\"
+.\" 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 2019 Joyent, Inc.
+.\"
+.Dd Apr 30, 2019
+.Dt DDI_UFM_IMAGE 9F
+.Os
+.Sh NAME
+.Nm ddi_ufm_image_set_desc ,
+.Nm ddi_ufm_image_set_misc ,
+.Nm ddi_ufm_image_set_nslots
+.Nd UFM image property routines
+.Sh SYNOPSIS
+.In sys/ddi_ufm.h
+.Ft void
+.Fo ddi_ufm_image_set_desc
+.Fa "ddi_ufm_image_t *uip"
+.Fa "const char *description"
+.Fc
+.Ft void
+.Fo ddi_ufm_image_set_misc
+.Fa "ddi_ufm_image_t *uip"
+.Fa "nvlist_t *nvl"
+.Fc
+.Ft void
+.Fo ddi_ufm_image_set_nslots
+.Fa "ddi_ufm_image_t *uip"
+.Fa "uint_t nslots"
+.Fc
+.Sh INTERFACE LEVEL
+.Sy Evolving -
+This interface is evolving still in illumos.
+API and ABI stability is not guaranteed.
+.Sh PARAMETERS
+.Bl -tag -width Fa
+.It Fa uip
+A pointer to a UFM image that was passed to the driver in its
+.Xr ddi_ufm_op_fill_image 9E
+entry point.
+.It Fa description
+A human-readable description of the firmware image.
+.It Fa nvl
+An nvlist_t with ancillary, device-specific data.
+.It Fa nslots
+The number of firmware slots supported by this firmare image.
+.El
+.Sh DESCRIPTION
+The
+.Fn ddi_ufm_image_set_desc ,
+.Fn ddi_ufm_image_set_misc
+and
+.Fn ddi_ufm_image_set_nslots
+functions are used by device drivers to set information about a firmware
+image on the image structure
+.Fa uip
+as a part of implementing their
+.Xr ddi_ufm_op_fill_image 9E
+entry point.
+For more information on images and the use of these functions, see the
+description of the
+.Fn ddi_ufm_op_fill_image
+function in
+.Xr ddi_ufm 9E .
+.Pp
+The
+.Fn ddi_ufm_image_set_desc
+function sets the description of the firmware image.
+This description is intended for administrators and should convey the intended
+use of the image.
+.Pp
+The
+.Fn ddi_ufm_image_set_misc
+function is used by drivers to set ancillary key-value data that may be
+useful to a consumer.
+The driver should create an nvlist for this purpose with
+.Xr nvlist_alloc 9F
+Once the driver passes the nvlist to the
+.Fn ddi_ufm_image_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_image_set_nslots
+function should be called to indicate the number of firmware slots supported
+by this firmware image.
+.Sh CONTEXT
+These functions should only be called in the context of the
+.Xr ddi_ufm_op_fill_image 9E
+entry point.
+.Sh SEE ALSO
+.Xr ddi_ufm 9E ,
+.Xr ddi_ufm_op_fill_image 9E ,
+.Xr nvlist_alloc 9F
diff --git a/usr/src/man/man9f/ddi_ufm_slot.9f b/usr/src/man/man9f/ddi_ufm_slot.9f
new file mode 100644
index 0000000000..c547905581
--- /dev/null
+++ b/usr/src/man/man9f/ddi_ufm_slot.9f
@@ -0,0 +1,111 @@
+.\"
+.\" 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 2019 Joyent, Inc.
+.\"
+.Dd Apr 30, 2019
+.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
+.Nd UFM slot property routines
+.Sh SYNOPSIS
+#include <sys/ddi_ufm.h>
+.sp
+.Ft void
+.Fo ddi_ufm_slot_set_version
+.Fa "ddi_ufm_slot_t *usp"
+.Fa "const char *version"
+.Fc
+.Ft void
+.Fo ddi_ufm_slot_set_attrs
+.Fa "ddi_ufm_slot_t *usp"
+.Fa "ddi_ufm_attr_t attrs"
+.Fc
+.Ft void
+.Fo ddi_ufm_slot_set_misc
+.Fa "ddi_ufm_slot_t *usp"
+.Fa "nvlist_t *nvl"
+.Fc
+.Sh INTERFACE LEVEL
+.Sy Evolving -
+This interface is evolving still in illumos.
+API and ABI stability is not guaranteed.
+.Sh PARAMETERS
+.Bl -tag -width Fa
+.It Fa usp
+A pointer to a UFM slot structure that was passed to the driver in its
+.Xr ddi_ufm_op_fill_slot 9E
+entry point.
+.It Fa version
+A device-specific ASCII string that indicates the current version of the
+firmware image in the slot.
+.It Fa attrs
+The bitwise-inclusive-OR of one of several attributes of a firmware
+slot.
+See the discussion of the
+.Fn ddi_ufm_op_fill_slot
+function in
+.Xr ddi_ufm 9E .
+.It Fa nvl
+An nvlist_t with ancillary, device-specific data.
+.El
+.Sh DESCRIPTION
+The
+.Fn ddi_ufm_slot_set_version ,
+.Fn ddi_ufm_slot_set_attrs ,
+and
+.Fn ddi_ufm_slot_set_misc
+functions are used by device drivers to set information about a firmware
+slot on the slot structure
+.Fa usp
+as a part of implementing their
+.Xr ddi_ufm_op_fill_slot 9E
+entry point.
+For more information on slots and the use of these functions, see the
+description of the
+.Fn ddi_ufm_op_fill_slot
+function in
+.Xr ddi_ufm 9E .
+.Pp
+The
+.Fn ddi_ufm_slot_set_version
+function sets the version property of a firmware slot.
+The version should be a human-readable ASCII string that describes the current
+firmware revision in a way that makes sense to an administrator and someone
+who is referencing the documentation of a vendor.
+.Pp
+The
+.Fn ddi_ufm_slot_set_attrs
+function describes attributes of a UFM slot as defined by the
+ddi_ufm_attr_t enum.
+.Pp
+The
+.Fn ddi_ufm_slot_set_misc
+function is used by the driver to set ancillary key-value data that may
+be useful to a consumer.
+For example, a driver may use this method to encode specific information that
+the firmware provides about how or when it was produced or installed on the
+device.
+The driver should create an nvlist for this purpose with
+.Xr nvlist_alloc 9F .
+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.
+.Sh CONTEXT
+These functions should only be called in the context of the
+.Xr ddi_ufm_op_fill_slot 9E
+entry point.
+.Sh SEE ALSO
diff --git a/usr/src/pkg/manifests/system-header.mf b/usr/src/pkg/manifests/system-header.mf
index 2f7f8906ee..db0e188609 100644
--- a/usr/src/pkg/manifests/system-header.mf
+++ b/usr/src/pkg/manifests/system-header.mf
@@ -899,6 +899,8 @@ file path=usr/include/sys/ddi_intr_impl.h
file path=usr/include/sys/ddi_isa.h
file path=usr/include/sys/ddi_obsolete.h
file path=usr/include/sys/ddi_periodic.h
+file path=usr/include/sys/ddi_ufm.h
+file path=usr/include/sys/ddi_ufm_impl.h
file path=usr/include/sys/ddidevmap.h
file path=usr/include/sys/ddidmareq.h
file path=usr/include/sys/ddifm.h
diff --git a/usr/src/pkg/manifests/system-io-tests.mf b/usr/src/pkg/manifests/system-io-tests.mf
index 68d0a51b1a..4794852e10 100644
--- a/usr/src/pkg/manifests/system-io-tests.mf
+++ b/usr/src/pkg/manifests/system-io-tests.mf
@@ -21,6 +21,7 @@
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2019 Joyent, Inc.
#
set name=pkg.fmri value=pkg:/system/io/tests@$(PKGVERS)
@@ -48,8 +49,10 @@ file path=usr/kernel/drv/$(ARCH64)/pshot group=sys
file path=usr/kernel/drv/$(ARCH64)/tclient group=sys
file path=usr/kernel/drv/$(ARCH64)/tphci group=sys
file path=usr/kernel/drv/$(ARCH64)/tvhci group=sys
+file path=usr/kernel/drv/$(ARCH64)/ufmtest group=sys
file path=usr/kernel/drv/emul64.conf group=sys
file path=usr/kernel/drv/pshot.conf group=sys
+file path=usr/kernel/drv/ufmtest.conf group=sys
file path=usr/lib/scsi/sestopo mode=0555
file path=usr/lib/scsi/smp mode=0555
file path=usr/sbin/devctl mode=0555
diff --git a/usr/src/pkg/manifests/system-kernel.man7d.inc b/usr/src/pkg/manifests/system-kernel.man7d.inc
index 375ba3896a..07da55bde8 100644
--- a/usr/src/pkg/manifests/system-kernel.man7d.inc
+++ b/usr/src/pkg/manifests/system-kernel.man7d.inc
@@ -12,6 +12,7 @@
#
# Copyright 2011, Richard Lowe
# Copyright 2016 Nexenta Systems, Inc.
+# Copyright 2019 Joyent, Inc.
#
file path=usr/share/man/man7d/bscv.7d
@@ -46,6 +47,7 @@ file path=usr/share/man/man7d/ticlts.7d
file path=usr/share/man/man7d/tty.7d
file path=usr/share/man/man7d/ttymux.7d
file path=usr/share/man/man7d/tzmon.7d
+file path=usr/share/man/man7d/ufm.7d
file path=usr/share/man/man7d/virtualkm.7d
file path=usr/share/man/man7d/vni.7d
file path=usr/share/man/man7d/wscons.7d
diff --git a/usr/src/pkg/manifests/system-kernel.man9e.inc b/usr/src/pkg/manifests/system-kernel.man9e.inc
index 600a2bf7f9..3513e98c4c 100644
--- a/usr/src/pkg/manifests/system-kernel.man9e.inc
+++ b/usr/src/pkg/manifests/system-kernel.man9e.inc
@@ -12,6 +12,7 @@
#
# Copyright 2011, Richard Lowe
# Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2019 Joyent, Inc.
#
file path=usr/share/man/man9e/Intro.9e
@@ -22,6 +23,7 @@ file path=usr/share/man/man9e/awrite.9e
file path=usr/share/man/man9e/chpoll.9e
file path=usr/share/man/man9e/close.9e
file path=usr/share/man/man9e/csx_event_handler.9e
+file path=usr/share/man/man9e/ddi_ufm.9e
file path=usr/share/man/man9e/detach.9e
file path=usr/share/man/man9e/devmap.9e
file path=usr/share/man/man9e/devmap_access.9e
@@ -82,6 +84,10 @@ link path=usr/share/man/man9e/GLDv3.9e target=mac.9e
link path=usr/share/man/man9e/MAC.9e target=mac.9e
link path=usr/share/man/man9e/_info.9e target=_fini.9e
link path=usr/share/man/man9e/_init.9e target=_fini.9e
+link path=usr/share/man/man9e/ddi_ufm_op_fill_image.9e target=ddi_ufm.9e
+link path=usr/share/man/man9e/ddi_ufm_op_fill_slot.9e target=ddi_ufm.9e
+link path=usr/share/man/man9e/ddi_ufm_op_getcaps.9e target=ddi_ufm.9e
+link path=usr/share/man/man9e/ddi_ufm_op_nimages.9e target=ddi_ufm.9e
link path=usr/share/man/man9e/gldm_get_stats.9e target=gld.9e
link path=usr/share/man/man9e/gldm_intr.9e target=gld.9e
link path=usr/share/man/man9e/gldm_ioctl.9e target=gld.9e
diff --git a/usr/src/pkg/manifests/system-kernel.man9f.inc b/usr/src/pkg/manifests/system-kernel.man9f.inc
index 0b8d21381c..4076044314 100644
--- a/usr/src/pkg/manifests/system-kernel.man9f.inc
+++ b/usr/src/pkg/manifests/system-kernel.man9f.inc
@@ -14,6 +14,7 @@
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
# Copyright 2016 Nexenta Systems, Inc.
# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+# Copyright 2019 Joyent, Inc.
#
file path=usr/share/man/man9f/ASSERT.9f
@@ -237,6 +238,9 @@ file path=usr/share/man/man9f/ddi_soft_state.9f
file path=usr/share/man/man9f/ddi_strtol.9f
file path=usr/share/man/man9f/ddi_strtoll.9f
file path=usr/share/man/man9f/ddi_strtoul.9f
+file path=usr/share/man/man9f/ddi_ufm.9f
+file path=usr/share/man/man9f/ddi_ufm_image.9f
+file path=usr/share/man/man9f/ddi_ufm_slot.9f
file path=usr/share/man/man9f/ddi_umem_alloc.9f
file path=usr/share/man/man9f/ddi_umem_iosetup.9f
file path=usr/share/man/man9f/ddi_umem_lock.9f
@@ -911,6 +915,15 @@ link path=usr/share/man/man9f/ddi_taskq_suspend.9f target=taskq.9f
link path=usr/share/man/man9f/ddi_taskq_wait.9f target=taskq.9f
link path=usr/share/man/man9f/ddi_trigger_softintr.9f \
target=ddi_add_softintr.9f
+link path=usr/share/man/man9f/ddi_ufm_fini.9f target=ddi_ufm.9f
+link path=usr/share/man/man9f/ddi_ufm_init.9f target=ddi_ufm.9f
+link path=usr/share/man/man9f/ddi_ufm_update.9f target=ddi_ufm.9f
+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_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
link path=usr/share/man/man9f/ddi_umem_unlock.9f target=ddi_umem_lock.9f
link path=usr/share/man/man9f/ddi_unmap_regs.9f target=ddi_map_regs.9f
diff --git a/usr/src/pkg/manifests/system-kernel.mf b/usr/src/pkg/manifests/system-kernel.mf
index 58e1aa8ff2..70fa5dd1dc 100644
--- a/usr/src/pkg/manifests/system-kernel.mf
+++ b/usr/src/pkg/manifests/system-kernel.mf
@@ -26,6 +26,7 @@
# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
# Copyright 2017 James S Blachly, MD <james.blachly@gmail.com>
# Copyright 2019 RackTop Systems
+# Copyright 2019 Joyent, Inc.
#
#
@@ -286,6 +287,7 @@ $(sparc_ONLY)driver name=uata \
$(i386_ONLY)driver name=ucode perms="* 0644 root sys"
driver name=udp perms="udp 0666 root sys"
driver name=udp6 perms="udp6 0666 root sys"
+driver name=ufm perms="ufm 0666 root sys"
$(i386_ONLY)driver name=vgatext \
alias=pciclass,000100 \
alias=pciclass,030000 \
@@ -406,6 +408,7 @@ $(sparc_ONLY)file path=kernel/drv/$(ARCH64)/uata group=sys
$(i386_ONLY)file path=kernel/drv/$(ARCH64)/ucode group=sys
file path=kernel/drv/$(ARCH64)/udp group=sys
file path=kernel/drv/$(ARCH64)/udp6 group=sys
+file path=kernel/drv/$(ARCH64)/ufm group=sys
$(i386_ONLY)file path=kernel/drv/$(ARCH64)/vgatext group=sys
file path=kernel/drv/$(ARCH64)/vnic group=sys
file path=kernel/drv/$(ARCH64)/wc group=sys
@@ -490,6 +493,7 @@ $(sparc_ONLY)file path=kernel/drv/uata.conf group=sys \
$(i386_ONLY)file path=kernel/drv/ucode.conf group=sys
file path=kernel/drv/udp.conf group=sys
file path=kernel/drv/udp6.conf group=sys
+file path=kernel/drv/ufm.conf group=sys
file path=kernel/drv/vnic.conf group=sys
file path=kernel/drv/wc.conf group=sys
$(sparc_ONLY)file path=kernel/exec/$(ARCH64)/aoutexec group=sys mode=0755
diff --git a/usr/src/pkg/manifests/system-test-ostest.mf b/usr/src/pkg/manifests/system-test-ostest.mf
index 935920cc83..78d46bdf6a 100644
--- a/usr/src/pkg/manifests/system-test-ostest.mf
+++ b/usr/src/pkg/manifests/system-test-ostest.mf
@@ -12,7 +12,7 @@
#
# Copyright (c) 2012, 2016 by Delphix. All rights reserved.
# Copyright 2014, OmniTI Computer Consulting, Inc. All rights reserved.
-# Copyright 2018 Joyent, Inc.
+# Copyright 2019 Joyent, Inc.
#
set name=pkg.fmri value=pkg:/system/test/ostest@$(PKGVERS)
@@ -25,6 +25,7 @@ dir path=opt/os-tests
dir path=opt/os-tests/bin
dir path=opt/os-tests/runfiles
dir path=opt/os-tests/tests
+dir path=opt/os-tests/tests/ddi_ufm
dir path=opt/os-tests/tests/file-locking
$(i386_ONLY)dir path=opt/os-tests/tests/i386
dir path=opt/os-tests/tests/pf_key
@@ -36,6 +37,9 @@ dir path=opt/os-tests/tests/stress
file path=opt/os-tests/README mode=0444
file path=opt/os-tests/bin/ostest mode=0555
file path=opt/os-tests/runfiles/default.run mode=0444
+file path=opt/os-tests/tests/ddi_ufm/ufm-test mode=0555
+file path=opt/os-tests/tests/ddi_ufm/ufm-test-cleanup mode=0555
+file path=opt/os-tests/tests/ddi_ufm/ufm-test-setup mode=0555
file path=opt/os-tests/tests/epoll_test mode=0555
file path=opt/os-tests/tests/file-locking/acquire-lock.32 mode=0555
file path=opt/os-tests/tests/file-locking/acquire-lock.64 mode=0555
@@ -76,4 +80,5 @@ file path=opt/os-tests/tests/stress/dladm-kstat mode=0555
license cr_Sun license=cr_Sun
license lic_CDDL license=lic_CDDL
depend fmri=pkg:/network/telnet type=require
+depend fmri=system/io/tests type=require
depend fmri=system/test/testrunner type=require
diff --git a/usr/src/test/os-tests/runfiles/default.run b/usr/src/test/os-tests/runfiles/default.run
index d67642f32b..1b6cbfb6c8 100644
--- a/usr/src/test/os-tests/runfiles/default.run
+++ b/usr/src/test/os-tests/runfiles/default.run
@@ -11,7 +11,7 @@
#
# Copyright (c) 2012 by Delphix. All rights reserved.
-# Copyright 2018 Joyent, Inc.
+# Copyright 2019 Joyent, Inc.
#
[DEFAULT]
@@ -63,6 +63,10 @@ tests = ['conn', 'dgram', 'drop_priv', 'nosignal', 'sockpair']
user = root
tests = ['acquire-compare', 'acquire-spray']
+[/opt/os-tests/tests/ddi_ufm]
+user = root
+tests = ['ufm-test-setup', 'ufm-test', 'ufm-test-cleanup']
+
[/opt/os-tests/tests/i386]
user = root
arch = i86pc
diff --git a/usr/src/test/os-tests/tests/Makefile b/usr/src/test/os-tests/tests/Makefile
index 5de66f69e5..aab4828541 100644
--- a/usr/src/test/os-tests/tests/Makefile
+++ b/usr/src/test/os-tests/tests/Makefile
@@ -11,12 +11,22 @@
#
# Copyright (c) 2012, 2016 by Delphix. All rights reserved.
-# Copyright 2018 Joyent, Inc.
+# Copyright 2019 Joyent, Inc.
#
SUBDIRS_i386 = i386
-SUBDIRS = poll secflags sigqueue spoof-ras sdevfs sockfs stress file-locking \
- pf_key $(SUBDIRS_$(MACH))
+SUBDIRS = \
+ ddi_ufm \
+ file-locking \
+ pf_key \
+ poll \
+ sdevfs \
+ secflags \
+ sigqueue \
+ sockfs \
+ spoof-ras \
+ stress \
+ $(SUBDIRS_$(MACH))
include $(SRC)/test/Makefile.com
diff --git a/usr/src/test/os-tests/tests/ddi_ufm/Makefile b/usr/src/test/os-tests/tests/ddi_ufm/Makefile
new file mode 100644
index 0000000000..31646142b0
--- /dev/null
+++ b/usr/src/test/os-tests/tests/ddi_ufm/Makefile
@@ -0,0 +1,51 @@
+#
+# 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 2019 Joyent, Inc.
+#
+
+include $(SRC)/Makefile.master
+
+ROOTOPTPKG = $(ROOT)/opt/os-tests
+TESTDIR = $(ROOTOPTPKG)/tests/ddi_ufm
+
+PROGS = ufm-test-setup ufm-test ufm-test-cleanup
+
+include $(SRC)/cmd/Makefile.cmd
+include $(SRC)/test/Makefile.com
+
+LDLIBS += -lnvpair
+CFLAGS += -I$(SRC)/uts/common/io/
+CSTD= $(CSTD_GNU99)
+
+CMDS = $(PROGS:%=$(TESTDIR)/%)
+$(CMDS) := FILEMODE = 0555
+
+all: $(PROGS)
+
+install: all $(CMDS)
+
+lint:
+
+clobber: clean
+ -$(RM) $(PROGS)
+
+clean:
+ -$(RM) *.o
+
+$(CMDS): $(TESTDIR) $(PROGS)
+
+$(TESTDIR):
+ $(INS.dir)
+
+$(TESTDIR)/%: %
+ $(INS.file)
diff --git a/usr/src/test/os-tests/tests/ddi_ufm/ufm-test-cleanup.sh b/usr/src/test/os-tests/tests/ddi_ufm/ufm-test-cleanup.sh
new file mode 100644
index 0000000000..f71951511f
--- /dev/null
+++ b/usr/src/test/os-tests/tests/ddi_ufm/ufm-test-cleanup.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/bash
+
+#
+# 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 2019 Joyent, Inc.
+#
+
+grep ufmtest /etc/name_to_major &> /dev/null
+if [[ $? -eq 1 ]]; then
+ printf "ufmtest driver is not currently installed\n"
+ exit 0
+fi
+
+printf "Removing ufmtest driver ...\n"
+/usr/sbin/rem_drv ufmtest
+if [[ $? -ne 0 ]]; then
+ printf "Failed to remove the ufmtest driver.\n" 1>&2
+ exit 1
+else
+ exit 0
+fi
+
diff --git a/usr/src/test/os-tests/tests/ddi_ufm/ufm-test-setup.sh b/usr/src/test/os-tests/tests/ddi_ufm/ufm-test-setup.sh
new file mode 100644
index 0000000000..6895b2f479
--- /dev/null
+++ b/usr/src/test/os-tests/tests/ddi_ufm/ufm-test-setup.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/bash
+
+#
+# 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 2019 Joyent, Inc.
+#
+
+grep ufmtest /etc/name_to_major &>/dev/null
+if [[ $? -eq 0 ]]; then
+ printf "ufmtest driver is already installed\n"
+ exit 0
+fi
+
+printf "Installing ufmtest driver ... \n"
+/usr/sbin/add_drv -v -f ufmtest
+
+if [[ $? -ne 0 ]]; then
+ printf "%s\n%s\n" "Failed to install the ufmtest driver." \
+ "Verify that the IPS package system/io/tests is installed." 1>&2
+ exit 1
+else
+ exit 0
+fi
+
diff --git a/usr/src/test/os-tests/tests/ddi_ufm/ufm-test.c b/usr/src/test/os-tests/tests/ddi_ufm/ufm-test.c
new file mode 100644
index 0000000000..2b6bf2db4e
--- /dev/null
+++ b/usr/src/test/os-tests/tests/ddi_ufm/ufm-test.c
@@ -0,0 +1,897 @@
+/*
+ * 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 2019 Joyent, Inc.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libnvpair.h>
+#include <string.h>
+#include <stropts.h>
+#include <unistd.h>
+#include <sys/debug.h>
+#include <sys/ddi_ufm.h>
+#include <sys/types.h>
+#include <sys/varargs.h>
+
+#include "ufmtest.h"
+
+#define ERRNO_ANY -1
+#define UFMTEST_DEV "/pseudo/ufmtest@0"
+
+static const char *pname;
+
+struct ufm_test_state {
+ uint_t ufts_n_run;
+ uint_t ufts_n_passes;
+ uint_t ufts_n_fails;
+ int ufts_ufm_fd;
+ int ufts_ufmtest_fd;
+};
+
+#define MAX_IMAGES 5
+#define MAX_SLOTS 5
+#define MAX_STR 128
+struct ufm_test_slot_data {
+ const char us_vers[MAX_STR];
+ int us_attrs;
+ int us_nmisc;
+};
+
+struct ufm_test_img_data {
+ const char ui_desc[MAX_STR];
+ int ui_nslots;
+ int ui_nmisc;
+ struct ufm_test_slot_data ui_slots[MAX_SLOTS];
+};
+
+struct ufm_test_data {
+ uint_t ud_nimages;
+ struct ufm_test_img_data ud_images[MAX_IMAGES];
+};
+
+#define NO_SLOT {"", -1, -1}
+#define NO_IMG {"", -1, -1, {NO_SLOT, NO_SLOT, NO_SLOT, NO_SLOT, NO_SLOT}}
+
+/*
+ * 3 images w\
+ * - 1 slot
+ * - 2 slots (1st active)
+ * - 3 slots (1st active, 3rd empty)
+ */
+const struct ufm_test_data fw_data1 = {
+ 3,
+ {
+ {"fw image 1", 1, 0, {
+ {"1.0", 4, 0 }, NO_SLOT, NO_SLOT, NO_SLOT, NO_SLOT }},
+ {"fw image 2", 2, 0, {
+ {"1.0", 4, 0 }, {"1.1", 0, 0}, NO_SLOT, NO_SLOT, NO_SLOT }},
+ {"fw image 3", 3, 0, {
+ {"1.0", 4, 0 }, {"1.1", 0, 0}, {"", 8, 0}, NO_SLOT, NO_SLOT }},
+ NO_IMG,
+ NO_IMG
+ }
+};
+
+/*
+ * Generate an ISO 8601 timestamp
+ */
+static void
+get_timestamp(char *buf, size_t bufsize)
+{
+ time_t utc_time;
+ struct tm *p_tm;
+
+ (void) time(&utc_time);
+ p_tm = localtime(&utc_time);
+
+ (void) strftime(buf, bufsize, "%FT%TZ", p_tm);
+}
+
+/* PRINTFLIKE1 */
+static void
+logmsg(const char *format, ...)
+{
+ char timestamp[128];
+ va_list ap;
+
+ get_timestamp(timestamp, sizeof (timestamp));
+ (void) fprintf(stdout, "%s ", timestamp);
+ va_start(ap, format);
+ (void) vfprintf(stdout, format, ap);
+ va_end(ap);
+ (void) fprintf(stdout, "\n");
+ (void) fflush(stdout);
+}
+
+static int
+do_test_setup(struct ufm_test_state *tst_state)
+{
+ if ((tst_state->ufts_ufm_fd = open(DDI_UFM_DEV, O_RDONLY)) < 0) {
+ logmsg("failed to open %s (%s)", DDI_UFM_DEV,
+ strerror(errno));
+ return (-1);
+ }
+ if ((tst_state->ufts_ufmtest_fd = open("/dev/ufmtest", O_RDONLY)) < 0) {
+ logmsg("failed to open /dev/ufmtest (%s)",
+ strerror(errno));
+ return (0);
+ }
+ return (0);
+}
+
+static void
+free_nvlist_arr(nvlist_t **nvlarr, uint_t nelems)
+{
+ for (uint_t i = 0; i < nelems; i++) {
+ if (nvlarr[i] != NULL)
+ nvlist_free(nvlarr[i]);
+ }
+ free(nvlarr);
+}
+
+static int
+do_setfw(struct ufm_test_state *tst_state, const struct ufm_test_data *fwdata)
+{
+ ufmtest_ioc_setfw_t ioc = { 0 };
+ nvlist_t *nvl = NULL, **images = NULL, **slots = NULL;
+ int ret = -1;
+
+ if ((images = calloc(sizeof (nvlist_t *), fwdata->ud_nimages)) == NULL)
+ return (-1);
+
+ for (uint_t i = 0; i < fwdata->ud_nimages; i++) {
+ if (nvlist_alloc(&images[i], NV_UNIQUE_NAME, 0) != 0 ||
+ nvlist_add_string(images[i], DDI_UFM_NV_IMAGE_DESC,
+ fwdata->ud_images[i].ui_desc) != 0) {
+ goto out;
+ }
+ if ((slots = calloc(sizeof (nvlist_t *),
+ fwdata->ud_images[i].ui_nslots)) == NULL) {
+ goto out;
+ }
+
+ for (int s = 0; s < fwdata->ud_images[i].ui_nslots; s++) {
+ if (nvlist_alloc(&slots[s], NV_UNIQUE_NAME, 0) != 0 ||
+ nvlist_add_string(slots[s], DDI_UFM_NV_SLOT_VERSION,
+ fwdata->ud_images[i].ui_slots[s].us_vers) != 0 ||
+ nvlist_add_uint32(slots[s], DDI_UFM_NV_SLOT_ATTR,
+ fwdata->ud_images[i].ui_slots[s].us_attrs) != 0) {
+
+ free_nvlist_arr(slots,
+ fwdata->ud_images[i].ui_nslots);
+ goto out;
+ }
+ }
+
+ if (nvlist_add_nvlist_array(images[i], DDI_UFM_NV_IMAGE_SLOTS,
+ slots, fwdata->ud_images[i].ui_nslots) != 0) {
+ free_nvlist_arr(slots, fwdata->ud_images[i].ui_nslots);
+ goto out;
+ }
+ free_nvlist_arr(slots, fwdata->ud_images[i].ui_nslots);
+ }
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
+ nvlist_add_nvlist_array(nvl, DDI_UFM_NV_IMAGES, images,
+ fwdata->ud_nimages) != 0) {
+ goto out;
+ }
+
+ if (nvlist_size(nvl, &ioc.utsw_bufsz, NV_ENCODE_NATIVE) != 0 ||
+ (ioc.utsw_buf = malloc(ioc.utsw_bufsz)) == NULL ||
+ nvlist_pack(nvl, &ioc.utsw_buf, &ioc.utsw_bufsz, NV_ENCODE_NATIVE,
+ 0) != 0) {
+ goto out;
+ }
+
+ if (ioctl(tst_state->ufts_ufmtest_fd, UFMTEST_IOC_SET_FW, &ioc) < 0) {
+ logmsg("UFMTEST_IOC_SET_FW ioctl failed (%s)",
+ strerror(errno));
+ return (-1);
+ }
+ ret = 0;
+out:
+ free_nvlist_arr(images, fwdata->ud_nimages);
+ nvlist_free(nvl);
+ free(ioc.utsw_buf);
+
+ return (ret);
+}
+
+static int
+do_toggle_fails(struct ufm_test_state *tst_state, uint32_t fail_flags)
+{
+ ufmtest_ioc_fails_t ioc = { 0 };
+
+ ioc.utfa_flags = fail_flags;
+
+ if (ioctl(tst_state->ufts_ufmtest_fd, UFMTEST_IOC_TOGGLE_FAILS,
+ &ioc) < 0) {
+ logmsg("UFMTEST_IOC_TOGGLE_FAILS ioctl failed (%s)",
+ strerror(errno));
+ return (1);
+ }
+ return (0);
+}
+
+static int
+do_update(struct ufm_test_state *tst_state)
+{
+ if (ioctl(tst_state->ufts_ufmtest_fd, UFMTEST_IOC_DO_UPDATE,
+ NULL) < 0) {
+ logmsg("UFMTEST_IOC_DO_UPDATE ioctl failed (%s)",
+ strerror(errno));
+ return (1);
+ }
+ return (0);
+}
+
+static int
+try_open(int oflag, int exp_errno)
+{
+ int fd;
+
+ fd = open(DDI_UFM_DEV, oflag);
+ if (fd != -1) {
+ logmsg("FAIL: expected open(2) to return -1");
+ (void) close(fd);
+ return (-1);
+ }
+ if (errno != exp_errno) {
+ logmsg("FAIL: expected errno to be set to %u (%s)\n"
+ "actual errno was %u (%s)", exp_errno, strerror(exp_errno),
+ errno, strerror(errno));
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+do_negative_open_tests(struct ufm_test_state *tst_state)
+{
+ /*
+ * Assertion: Opening /dev/ufm in write-only mode will fail with errno
+ * set to EINVAL;
+ */
+ logmsg("TEST ufm_open_negative_001: Open %s in write-only mode",
+ DDI_UFM_DEV);
+ if (try_open(O_WRONLY, EINVAL) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Opening /dev/ufm in read-write mode will fail with errno
+ * set to EINVAL;
+ */
+ logmsg("TEST ufm_open_negative_002: Open %s in read-write mode",
+ DDI_UFM_DEV);
+ if (try_open(O_RDWR, EINVAL) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Opening /dev/ufm in exclusive mode will fail with errno
+ * set to EINVAL;
+ */
+ logmsg("TEST ufm_open_negative_003: Open %s in exclusive mode",
+ DDI_UFM_DEV);
+ if (try_open(O_RDONLY | O_EXCL, EINVAL) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Opening /dev/ufm in non-blocking mode will fail with errno
+ * set to EINVAL;
+ */
+ logmsg("TEST ufm_open_negative_004: Open %s in non-block mode",
+ DDI_UFM_DEV);
+ if (try_open(O_RDONLY | O_NONBLOCK, EINVAL) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Opening /dev/ufm in no-delay mode will fail with errno
+ * set to EINVAL;
+ */
+ logmsg("TEST ufm_open_negative_005: Open %s in ndelay mode",
+ DDI_UFM_DEV);
+ if (try_open(O_RDONLY | O_NDELAY, EINVAL) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+}
+
+
+static int
+try_ioctl(int fd, int cmd, void *arg, int exp_errno)
+{
+ int ret;
+
+ ret = ioctl(fd, cmd, arg);
+ if (ret != -1) {
+ logmsg("FAIL: expected ioctl(2) to return -1");
+ (void) close(fd);
+ return (-1);
+ }
+ if (exp_errno != ERRNO_ANY && errno != exp_errno) {
+ logmsg("FAIL: expected errno to be set to %u (%s)\n"
+ "actual errno was %u (%s)", exp_errno, strerror(exp_errno),
+ errno, strerror(errno));
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * These are a set of negative test cases to verify the correctness and
+ * robustness of the DDI UFM ioctl interface.
+ */
+static void
+do_negative_ioctl_tests(struct ufm_test_state *tst_state)
+{
+ ufm_ioc_getcaps_t ugc = { 0 };
+ ufm_ioc_bufsz_t ubz = { 0 };
+ ufm_ioc_report_t urep = { 0 };
+ size_t reportsz;
+ char *buf;
+ uint_t i, j;
+
+ uint8_t not_ascii[MAXPATHLEN];
+ char no_nul[MAXPATHLEN];
+
+ for (uint_t i = 0; i < MAXPATHLEN; i++)
+ no_nul[i] = '%';
+
+ CTASSERT(MAXPATHLEN > 129);
+ for (i = 0, j = 128; j <= 256; i++, j++)
+ not_ascii[i] = j;
+
+ not_ascii[i] = '\0';
+
+ /*
+ * Seed the test driver with a set of valid firmware data
+ */
+ if (do_setfw(tst_state, &fw_data1) != 0) {
+ logmsg("Failed to seed ufmtest driver with fw data");
+ return;
+ }
+
+ /*
+ * Cache the report size, and create a buffer of that size,
+ * as we'll need them for some of the tests that follow.
+ */
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz) < 0) {
+ logmsg("Failed to get fw data report size");
+ return;
+ }
+ reportsz = ubz.ufbz_size;
+ if ((buf = malloc(reportsz)) == NULL) {
+ logmsg("Failed to allocate %u bytes to hold report");
+ return;
+ }
+
+ /*
+ * Assertion: Specifying a DDI UFM version that is out of range in the
+ * argument to UFM_IOC_GETCAPS will fail and set errno to ENOTSUP.
+ */
+ logmsg("TEST ufm_getcaps_negative_001: Bad DDI UFM version (too low)");
+ ugc.ufmg_version = 0;
+ (void) strlcpy(ugc.ufmg_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ENOTSUP) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_getcaps_negative_002: Bad DDI UFM version (too high)");
+ ugc.ufmg_version = 999;
+ (void) strlcpy(ugc.ufmg_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ENOTSUP) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Specifying a bad device pathname in the argument to
+ * UFM_IOC_GETCAPS will cause the ioctl to fail, but the driver will
+ * not hang or panic.
+ */
+ logmsg("TEST ufm_getcaps_negative_003: Bad devpath (empty)");
+ ugc.ufmg_version = DDI_UFM_CURRENT_VERSION;
+ ugc.ufmg_devpath[0] = '\0';
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_getcaps_negative_004: Bad devpath (not a device)");
+ (void) strlcpy(ugc.ufmg_devpath, "/usr/bin/ls", MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_getcaps_negative_005: Bad devpath (not UFM device)");
+ (void) strlcpy(ugc.ufmg_devpath, "/dev/stdout", MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_getcaps_negative_006: Bad devpath (no NUL term)");
+ (void) strncpy(ugc.ufmg_devpath, no_nul, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_getcaps_negative_007: Bad devpath (not ascii str)");
+ (void) strlcpy(ugc.ufmg_devpath, (char *)not_ascii, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Specifying a DDI UFM version that is out of range in the
+ * argument to UFM_IOC_REPORTSZ will fail and set errno to ENOTSUP.
+ */
+ logmsg("TEST ufm_reportsz_negative_001: Bad DDI UFM version (too low)");
+ ubz.ufbz_version = 0;
+ (void) strlcpy(ubz.ufbz_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ENOTSUP) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_reportsz_negative_002: Bad DDI UFM version (too "
+ "high)");
+ ubz.ufbz_version = 999;
+ (void) strlcpy(ubz.ufbz_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ENOTSUP) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Specifying a bad device pathname in the argument to
+ * UFM_IOC_REPORTSZ will cause the ioctl to fail, but the driver will
+ * not hang or panic.
+ */
+ logmsg("TEST ufm_reportsz_negative_003: Bad devpath (empty)");
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ ubz.ufbz_devpath[0] = '\0';
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_reportsz_negative_004: Bad devpath (not a device)");
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, "/usr/bin/ls", MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_reportsz_negative_005: Bad devpath (not UFM device)");
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, "/dev/stdout", MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_reportsz_negative_006: Bad devpath (no NUL term)");
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strncpy(ubz.ufbz_devpath, no_nul, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_reportsz_negative_007: Bad devpath (not ascii str)");
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, (char *)not_ascii, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Specifying a DDI UFM version that is out of range in the
+ * argument to UFM_IOC_REPORT will fail and set errno to ENOTSUP.
+ */
+ logmsg("TEST ufm_report_negative_001: Bad DDI UFM version (too low)");
+ urep.ufmr_version = 0;
+ urep.ufmr_bufsz = reportsz;
+ urep.ufmr_buf = buf;
+ (void) strlcpy(urep.ufmr_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ENOTSUP) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_report_negative_002: Bad DDI UFM version (too high)");
+ urep.ufmr_version = 999;
+ urep.ufmr_bufsz = reportsz;
+ urep.ufmr_buf = buf;
+ (void) strlcpy(urep.ufmr_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ENOTSUP) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Specifying a bad device pathname in the argument to
+ * UFM_IOC_REPORT will cause the ioctl to fail, but the driver will
+ * not hang or panic.
+ */
+ logmsg("TEST ufm_report_negative_003: Bad devpath (empty)");
+ urep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ urep.ufmr_bufsz = reportsz;
+ urep.ufmr_buf = buf;
+ urep.ufmr_devpath[0] = '\0';
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_report_negative_004: Bad devpath (not a device)");
+ urep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ urep.ufmr_bufsz = reportsz;
+ urep.ufmr_buf = buf;
+ (void) strlcpy(urep.ufmr_devpath, "/usr/bin/ls", MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_report_negative_005: Bad devpath (not UFM device)");
+ urep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ urep.ufmr_bufsz = reportsz;
+ urep.ufmr_buf = buf;
+ (void) strlcpy(urep.ufmr_devpath, "/dev/stdout", MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_report_negative_006: Bad devpath (no NUL term)");
+ urep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ urep.ufmr_bufsz = reportsz;
+ urep.ufmr_buf = buf;
+ (void) strncpy(urep.ufmr_devpath, no_nul, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ logmsg("TEST ufm_report_negative_007: Bad devpath (not ascii str)");
+ urep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ urep.ufmr_bufsz = reportsz;
+ urep.ufmr_buf = buf;
+ (void) strlcpy(urep.ufmr_devpath, (char *)not_ascii, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Passing a bufsz that is too small to the UFM_IOC_REPORT
+ * ioctl will cause the ioctl to fail, but the driver will not hang or
+ * panic.
+ */
+ logmsg("TEST ufm_report_negative_008: bad bufsz (too small)");
+ urep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ urep.ufmr_bufsz = 10;
+ urep.ufmr_buf = buf;
+ (void) strlcpy(urep.ufmr_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: Passing a bufsz that is too small to the UFM_IOC_REPORT
+ * ioctl will cause the ioctl to fail, but the driver will not hang or
+ * panic.
+ */
+ logmsg("TEST ufm_report_negative_009: bad buf (NULL pointer)");
+ urep.ufmr_version = DDI_UFM_CURRENT_VERSION;
+ urep.ufmr_bufsz = 10;
+ urep.ufmr_buf = NULL;
+ (void) strlcpy(urep.ufmr_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORT, &urep,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+}
+
+/*
+ * These are a set of negative test cases to verify the correctness and
+ * robustness of the DDI UFM subsystems when a driver UFM callback returns
+ * an error.
+ *
+ * For each callback, we do the following:
+ *
+ * 1. Toggle the callback failure via a UFMTEST_IOC_TOGGLE_FAILS ioctl
+ * 2. Force a ddi_ufm_update() via a UFMTEST_IOC_DO_UPDATE ioctl. This is
+ * done in order to invalidate any cached firmware data for this device.
+ * 3. Call UFM_IOC_REPORTSZ ioctl to force the ufm_cache_fill() codepath to
+ * be executed.
+ */
+static void
+do_negative_callback_tests(struct ufm_test_state *tst_state)
+{
+ ufm_ioc_getcaps_t ugc = { 0 };
+ ufm_ioc_bufsz_t ubz = { 0 };
+ uint32_t failflags;
+ boolean_t failed;
+
+ /*
+ * Seed the test driver with a set of valid firmware data
+ */
+ if (do_setfw(tst_state, &fw_data1) != 0) {
+ logmsg("Failed to seed ufmtest driver with fw data");
+ return;
+ }
+
+ /*
+ * Assertion: If a driver's ddi_ufm_op_getcaps callback returns a
+ * failure, the kernel should not hang or panic when servicing a
+ * UFM_IOC_REPORTSZ ioctl. Furthermore, the UFM_IOC_REPORTSZ ioctl
+ * should fail.
+ */
+ logmsg("TEST ufm_callback_negative_001: ddi_ufm_op_getcaps fails");
+ failed = B_FALSE;
+ failflags = UFMTEST_FAIL_GETCAPS;
+ if (do_toggle_fails(tst_state, failflags) != 0 ||
+ do_update(tst_state) != 0) {
+ failed = B_TRUE;
+ }
+
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ failed = B_TRUE;
+
+ if (failed)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: If a driver's ddi_ufm_op_getcaps callback returns a
+ * failure, the kernel should not hang or panic when servicing a
+ * UFM_IOC_GETCAPS ioctl for that device. Furthermore, the
+ * UFM_IOC_GETCAPS ioctl should fail.
+ */
+ logmsg("TEST ufm_callback_negative_002: ddi_ufm_op_getcaps fails");
+ failed = B_FALSE;
+ ugc.ufmg_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ugc.ufmg_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_GETCAPS, &ugc,
+ ERRNO_ANY) != 0)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: If a driver's ddi_ufm_op_nimages callback returns a
+ * failure, the kernel should not hang or panic when servicing a
+ * UFM_IOC_REPORTSZ ioctl. Furthermore, the UFM_IOC_REPORTSZ ioctl
+ * should fail.
+ */
+ logmsg("TEST ufm_callback_negative_003: ddi_ufm_op_nimages fails");
+ failed = B_FALSE;
+ failflags = UFMTEST_FAIL_NIMAGES;
+ if (do_toggle_fails(tst_state, failflags) != 0 ||
+ do_update(tst_state) != 0) {
+ failed = B_TRUE;
+ }
+
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ failed = B_TRUE;
+
+ if (failed)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: If a driver's ddi_ufm_op_fill_image callback returns a
+ * failure, the kernel should not hang or panic when servicing a
+ * UFM_IOC_REPORTSZ ioctl. Furthermore, the UFM_IOC_REPORTSZ ioctl
+ * should fail.
+ */
+ logmsg("TEST ufm_callback_negative_004: ddi_ufm_op_fill_image fails");
+ failed = B_FALSE;
+ failflags = UFMTEST_FAIL_FILLIMAGE;
+ if (do_toggle_fails(tst_state, failflags) != 0 ||
+ do_update(tst_state) != 0) {
+ failed = B_TRUE;
+ }
+
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ failed = B_TRUE;
+
+ if (failed)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /*
+ * Assertion: If a driver's ddi_ufm_op_fill_slot callback returns a
+ * failure, the kernel should not hang or panic when servicing a
+ * UFM_IOC_REPORTSZ ioctl. Furthermore, the UFM_IOC_REPORTSZ ioctl
+ * should fail.
+ */
+ logmsg("TEST ufm_callback_negative_005: ddi_ufm_op_fill_slot fails");
+ failed = B_FALSE;
+ failflags = UFMTEST_FAIL_FILLSLOT;
+ if (do_toggle_fails(tst_state, failflags) != 0 ||
+ do_update(tst_state) != 0) {
+ failed = B_TRUE;
+ }
+
+ ubz.ufbz_version = DDI_UFM_CURRENT_VERSION;
+ (void) strlcpy(ubz.ufbz_devpath, UFMTEST_DEV, MAXPATHLEN);
+ if (try_ioctl(tst_state->ufts_ufm_fd, UFM_IOC_REPORTSZ, &ubz,
+ ERRNO_ANY) != 0)
+ failed = B_TRUE;
+
+ if (failed)
+ tst_state->ufts_n_fails++;
+ else
+ tst_state->ufts_n_passes++;
+
+ tst_state->ufts_n_run++;
+
+ /* Unset the fail flags */
+ failflags = 0;
+ if (do_toggle_fails(tst_state, failflags) != 0)
+ logmsg("Failed to clear fail flags");
+}
+
+int
+main(int argc, char **argv)
+{
+ int status = EXIT_FAILURE;
+ struct ufm_test_state tst_state = { 0 };
+
+ pname = argv[0];
+
+ if (do_test_setup(&tst_state) != 0) {
+ logmsg("Test setup failed - exiting");
+ return (status);
+ }
+
+ do_negative_open_tests(&tst_state);
+
+ if (tst_state.ufts_ufmtest_fd > 0) {
+ do_negative_ioctl_tests(&tst_state);
+ do_negative_callback_tests(&tst_state);
+ }
+
+ logmsg("Number of Tests Run: %u", tst_state.ufts_n_run);
+ logmsg("Number of Passes: %u", tst_state.ufts_n_passes);
+ logmsg("Number of Fails : %u", tst_state.ufts_n_fails);
+ if (tst_state.ufts_n_fails == 0)
+ status = EXIT_SUCCESS;
+
+ (void) close(tst_state.ufts_ufm_fd);
+ if (tst_state.ufts_ufmtest_fd >= 0)
+ (void) close(tst_state.ufts_ufmtest_fd);
+ return (status);
+}
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index 1c31820384..38850cbcf3 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -152,6 +152,7 @@ GENUNIX_OBJS += \
ddi_intr_irm.o \
ddi_nodeid.o \
ddi_periodic.o \
+ ddi_ufm.o \
devcfg.o \
devcache.o \
device.o \
diff --git a/usr/src/uts/common/io/i40e/i40e_main.c b/usr/src/uts/common/io/i40e/i40e_main.c
index e3707a8342..003997a408 100644
--- a/usr/src/uts/common/io/i40e/i40e_main.c
+++ b/usr/src/uts/common/io/i40e/i40e_main.c
@@ -1420,6 +1420,9 @@ i40e_unconfigure(dev_info_t *devinfo, i40e_t *i40e)
if (i40e->i40e_attach_progress & I40E_ATTACH_FM_INIT)
i40e_fm_fini(i40e);
+ if (i40e->i40e_attach_progress & I40E_ATTACH_UFM_INIT)
+ ddi_ufm_fini(i40e->i40e_ufmh);
+
kmem_free(i40e->i40e_aqbuf, I40E_ADMINQ_BUFSZ);
kmem_free(i40e, sizeof (i40e_t));
@@ -2033,7 +2036,7 @@ i40e_set_shared_vsi_props(i40e_t *i40e,
info->mapping_flags = LE_16(I40E_AQ_VSI_QUE_MAP_CONTIG);
info->queue_mapping[0] =
LE_16((vsi_qp_base << I40E_AQ_VSI_QUEUE_SHIFT) &
- I40E_AQ_VSI_QUEUE_MASK);
+ I40E_AQ_VSI_QUEUE_MASK);
/*
* tc_queues determines the size of the traffic class, where
@@ -2041,12 +2044,12 @@ i40e_set_shared_vsi_props(i40e_t *i40e,
* and 128 for the X722.
*
* Some examples:
- * i40e_num_trqpairs_per_vsi == 1 => tc_queues = 0, 2^^0 = 1.
- * i40e_num_trqpairs_per_vsi == 7 => tc_queues = 3, 2^^3 = 8.
- * i40e_num_trqpairs_per_vsi == 8 => tc_queues = 3, 2^^3 = 8.
- * i40e_num_trqpairs_per_vsi == 9 => tc_queues = 4, 2^^4 = 16.
- * i40e_num_trqpairs_per_vsi == 17 => tc_queues = 5, 2^^5 = 32.
- * i40e_num_trqpairs_per_vsi == 64 => tc_queues = 6, 2^^6 = 64.
+ * i40e_num_trqpairs_per_vsi == 1 => tc_queues = 0, 2^^0 = 1.
+ * i40e_num_trqpairs_per_vsi == 7 => tc_queues = 3, 2^^3 = 8.
+ * i40e_num_trqpairs_per_vsi == 8 => tc_queues = 3, 2^^3 = 8.
+ * i40e_num_trqpairs_per_vsi == 9 => tc_queues = 4, 2^^4 = 16.
+ * i40e_num_trqpairs_per_vsi == 17 => tc_queues = 5, 2^^5 = 32.
+ * i40e_num_trqpairs_per_vsi == 64 => tc_queues = 6, 2^^6 = 64.
*/
tc_queues = ddi_fls(i40e->i40e_num_trqpairs_per_vsi - 1);
@@ -2057,9 +2060,9 @@ i40e_set_shared_vsi_props(i40e_t *i40e,
*/
info->tc_mapping[0] =
LE_16(((0 << I40E_AQ_VSI_TC_QUE_OFFSET_SHIFT) &
- I40E_AQ_VSI_TC_QUE_OFFSET_MASK) |
- ((tc_queues << I40E_AQ_VSI_TC_QUE_NUMBER_SHIFT) &
- I40E_AQ_VSI_TC_QUE_NUMBER_MASK));
+ I40E_AQ_VSI_TC_QUE_OFFSET_MASK) |
+ ((tc_queues << I40E_AQ_VSI_TC_QUE_NUMBER_SHIFT) &
+ I40E_AQ_VSI_TC_QUE_NUMBER_MASK));
/*
* I40E_AQ_VSI_PVLAN_MODE_ALL ("VLAN driver insertion mode")
@@ -2394,7 +2397,7 @@ out:
/*
* Set up RSS.
- * 1. Seed the hash key.
+ * 1. Seed the hash key.
* 2. Enable PCTYPEs for the hash filter.
* 3. Populate the LUT.
*/
@@ -3208,6 +3211,85 @@ i40e_drain_rx(i40e_t *i40e)
return (B_TRUE);
}
+/*
+ * DDI UFM Callbacks
+ */
+static int
+i40e_ufm_fill_image(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ ddi_ufm_image_t *img)
+{
+ if (imgno != 0)
+ return (EINVAL);
+
+ ddi_ufm_image_set_desc(img, "Firmware");
+ ddi_ufm_image_set_nslots(img, 1);
+
+ return (0);
+}
+
+static int
+i40e_ufm_fill_slot(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ uint_t slotno, ddi_ufm_slot_t *slot)
+{
+ i40e_t *i40e = (i40e_t *)arg;
+ char *fw_ver = NULL, *fw_bld = NULL, *api_ver = NULL;
+ nvlist_t *misc = NULL;
+ uint_t flags = DDI_PROP_DONTPASS;
+ int err;
+
+ if (imgno != 0 || slotno != 0 ||
+ ddi_prop_lookup_string(DDI_DEV_T_ANY, i40e->i40e_dip, flags,
+ "firmware-version", &fw_ver) != DDI_PROP_SUCCESS ||
+ ddi_prop_lookup_string(DDI_DEV_T_ANY, i40e->i40e_dip, flags,
+ "firmware-build", &fw_bld) != DDI_PROP_SUCCESS ||
+ ddi_prop_lookup_string(DDI_DEV_T_ANY, i40e->i40e_dip, flags,
+ "api-version", &api_ver) != DDI_PROP_SUCCESS) {
+ err = EINVAL;
+ goto err;
+ }
+
+ ddi_ufm_slot_set_attrs(slot, DDI_UFM_ATTR_ACTIVE);
+ ddi_ufm_slot_set_version(slot, fw_ver);
+
+ (void) nvlist_alloc(&misc, NV_UNIQUE_NAME, KM_SLEEP);
+ if ((err = nvlist_add_string(misc, "firmware-build", fw_bld)) != 0 ||
+ (err = nvlist_add_string(misc, "api-version", api_ver)) != 0) {
+ goto err;
+ }
+ ddi_ufm_slot_set_misc(slot, misc);
+
+ ddi_prop_free(fw_ver);
+ ddi_prop_free(fw_bld);
+ ddi_prop_free(api_ver);
+
+ return (0);
+err:
+ nvlist_free(misc);
+ if (fw_ver != NULL)
+ ddi_prop_free(fw_ver);
+ if (fw_bld != NULL)
+ ddi_prop_free(fw_bld);
+ if (api_ver != NULL)
+ ddi_prop_free(api_ver);
+
+ return (err);
+}
+
+static int
+i40e_ufm_getcaps(ddi_ufm_handle_t *ufmh, void *arg, ddi_ufm_cap_t *caps)
+{
+ *caps = DDI_UFM_CAP_REPORT;
+
+ return (0);
+}
+
+static ddi_ufm_ops_t i40e_ufm_ops = {
+ NULL,
+ i40e_ufm_fill_image,
+ i40e_ufm_fill_slot,
+ i40e_ufm_getcaps
+};
+
static int
i40e_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
{
@@ -3323,6 +3405,14 @@ i40e_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd)
}
i40e->i40e_attach_progress |= I40E_ATTACH_ENABLE_INTR;
+ if (ddi_ufm_init(i40e->i40e_dip, DDI_UFM_CURRENT_VERSION, &i40e_ufm_ops,
+ &i40e->i40e_ufmh, i40e) != 0) {
+ i40e_error(i40e, "failed to initialize UFM subsystem");
+ goto attach_fail;
+ }
+ ddi_ufm_update(i40e->i40e_ufmh);
+ i40e->i40e_attach_progress |= I40E_ATTACH_UFM_INIT;
+
atomic_or_32(&i40e->i40e_state, I40E_INITIALIZED);
mutex_enter(&i40e_glock);
diff --git a/usr/src/uts/common/io/i40e/i40e_sw.h b/usr/src/uts/common/io/i40e/i40e_sw.h
index e7b64c2160..4bd0a58c2a 100644
--- a/usr/src/uts/common/io/i40e/i40e_sw.h
+++ b/usr/src/uts/common/io/i40e/i40e_sw.h
@@ -70,6 +70,7 @@ extern "C" {
#include <sys/list.h>
#include <sys/debug.h>
#include <sys/sdt.h>
+#include <sys/ddi_ufm.h>
#include "i40e_type.h"
#include "i40e_osdep.h"
#include "i40e_prototype.h"
@@ -148,7 +149,7 @@ typedef enum i40e_itr_index {
I40E_ITR_INDEX_RX = 0x0,
I40E_ITR_INDEX_TX = 0x1,
I40E_ITR_INDEX_OTHER = 0x2,
- I40E_ITR_INDEX_NONE = 0x3
+ I40E_ITR_INDEX_NONE = 0x3
} i40e_itr_index_t;
/*
@@ -335,13 +336,14 @@ typedef enum i40e_attach_state {
I40E_ATTACH_ALLOC_INTR = 0x0008, /* Interrupts allocated */
I40E_ATTACH_ALLOC_RINGSLOCKS = 0x0010, /* Rings & locks allocated */
I40E_ATTACH_ADD_INTR = 0x0020, /* Intr handlers added */
- I40E_ATTACH_COMMON_CODE = 0x0040, /* Intel code initialized */
+ I40E_ATTACH_COMMON_CODE = 0x0040, /* Intel code initialized */
I40E_ATTACH_INIT = 0x0080, /* Device initialized */
I40E_ATTACH_STATS = 0x0200, /* Kstats created */
I40E_ATTACH_MAC = 0x0800, /* MAC registered */
I40E_ATTACH_ENABLE_INTR = 0x1000, /* DDI interrupts enabled */
I40E_ATTACH_FM_INIT = 0x2000, /* FMA initialized */
I40E_ATTACH_LINK_TIMER = 0x4000, /* link check timer */
+ I40E_ATTACH_UFM_INIT = 0x8000, /* DDI UFM initialized */
} i40e_attach_state_t;
@@ -353,12 +355,12 @@ typedef enum i40e_attach_state {
* I40E_INITIALIZED: The device has been fully attached.
* I40E_STARTED: The device has come out of the GLDV3 start routine.
* I40E_SUSPENDED: The device is suspended and I/O among other things
- * should not occur. This happens because of an actual
- * DDI_SUSPEND or interrupt adjustments.
+ * should not occur. This happens because of an actual
+ * DDI_SUSPEND or interrupt adjustments.
* I40E_STALL: The tx stall detection logic has found a stall.
* I40E_OVERTEMP: The device has encountered a temperature alarm.
* I40E_INTR_ADJUST: Our interrupts are being manipulated and therefore we
- * shouldn't be manipulating their state.
+ * shouldn't be manipulating their state.
* I40E_ERROR: We've detected an FM error and degraded the device.
*/
typedef enum i40e_state {
@@ -590,7 +592,7 @@ typedef struct i40e_trqpair {
*/
i40e_dma_buffer_t itrq_desc_area; /* DMA buffer of tx desc ring */
i40e_tx_desc_t *itrq_desc_ring; /* TX Desc ring */
- volatile uint32_t *itrq_desc_wbhead; /* TX write-back index */
+ volatile uint32_t *itrq_desc_wbhead; /* TX write-back index */
uint32_t itrq_desc_head; /* Last index hw freed */
uint32_t itrq_desc_tail; /* Index of next free desc */
uint32_t itrq_desc_free; /* Number of free descriptors */
@@ -823,7 +825,7 @@ typedef struct i40e {
struct i40e_hw i40e_hw_space;
struct i40e_osdep i40e_osdep_space;
struct i40e_aq_get_phy_abilities_resp i40e_phy;
- void *i40e_aqbuf;
+ void *i40e_aqbuf;
#define I40E_DEF_VSI_IDX 0
#define I40E_DEF_VSI(i40e) ((i40e)->i40e_vsis[I40E_DEF_VSI_IDX])
@@ -856,7 +858,7 @@ typedef struct i40e {
* Transmit and receive information, tunables, and MAC info.
*/
i40e_trqpair_t *i40e_trqpairs;
- boolean_t i40e_mr_enable;
+ boolean_t i40e_mr_enable;
uint_t i40e_num_trqpairs; /* total TRQPs (per PF) */
uint_t i40e_num_trqpairs_per_vsi; /* TRQPs per VSI */
uint_t i40e_other_itr;
@@ -932,6 +934,9 @@ typedef struct i40e {
*/
uint32_t i40e_led_status;
boolean_t i40e_led_saved;
+
+ /* DDI UFM handle */
+ ddi_ufm_handle_t *i40e_ufmh;
} i40e_t;
/*
diff --git a/usr/src/uts/common/io/scsi/adapters/mpt_sas/mptsas.c b/usr/src/uts/common/io/scsi/adapters/mpt_sas/mptsas.c
index e5aa96f469..4784a6b1a6 100644
--- a/usr/src/uts/common/io/scsi/adapters/mpt_sas/mptsas.c
+++ b/usr/src/uts/common/io/scsi/adapters/mpt_sas/mptsas.c
@@ -22,7 +22,7 @@
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2016 Nexenta Systems, Inc. All rights reserved.
- * Copyright (c) 2018, Joyent, Inc.
+ * Copyright 2019 Joyent, Inc.
* Copyright 2014 OmniTI Computer Consulting, Inc. All rights reserved.
* Copyright (c) 2014, Tegile Systems Inc. All rights reserved.
*/
@@ -131,7 +131,17 @@ static int mptsas_quiesce(dev_info_t *devi);
#endif /* __sparc */
/*
- * Resource initilaization for hardware
+ * ddi_ufm_ops
+ */
+static int mptsas_ufm_fill_image(ddi_ufm_handle_t *ufmh, void *arg,
+ uint_t imgno, ddi_ufm_image_t *img);
+static int mptsas_ufm_fill_slot(ddi_ufm_handle_t *ufmh, void *arg,
+ uint_t imgno, uint_t slotno, ddi_ufm_slot_t *slot);
+static int mptsas_ufm_getcaps(ddi_ufm_handle_t *ufmh, void *arg,
+ ddi_ufm_cap_t *caps);
+
+/*
+ * Resource initialization for hardware
*/
static void mptsas_setup_cmd_reg(mptsas_t *mpt);
static void mptsas_disable_bus_master(mptsas_t *mpt);
@@ -559,6 +569,12 @@ static struct dev_ops mptsas_ops = {
#endif /* __sparc */
};
+static ddi_ufm_ops_t mptsas_ufm_ops = {
+ NULL,
+ mptsas_ufm_fill_image,
+ mptsas_ufm_fill_slot,
+ mptsas_ufm_getcaps
+};
#define MPTSAS_MOD_STRING "MPTSAS HBA Driver 00.00.00.24"
@@ -1237,6 +1253,15 @@ mptsas_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
mptsas_fm_init(mpt);
+ /*
+ * Initialize us with the UFM subsystem
+ */
+ if (ddi_ufm_init(dip, DDI_UFM_CURRENT_VERSION, &mptsas_ufm_ops,
+ &mpt->m_ufmh, mpt) != 0) {
+ mptsas_log(mpt, CE_WARN, "failed to initialize UFM subsystem");
+ goto fail;
+ }
+
if (mptsas_alloc_handshake_msg(mpt,
sizeof (Mpi2SCSITaskManagementRequest_t)) == DDI_FAILURE) {
mptsas_log(mpt, CE_WARN, "cannot initialize handshake msg.");
@@ -1537,6 +1562,9 @@ mptsas_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
* After this point, we are not going to fail the attach.
*/
+ /* Let the UFM susbsystem know we're ready to receive callbacks */
+ ddi_ufm_update(mpt->m_ufmh);
+
/* Print message of HBA present */
ddi_report_dev(dip);
@@ -1876,6 +1904,9 @@ mptsas_do_detach(dev_info_t *dip)
if (!mpt) {
return (DDI_FAILURE);
}
+
+ ddi_ufm_fini(mpt->m_ufmh);
+
/*
* Still have pathinfo child, should not detach mpt driver
*/
@@ -16969,3 +17000,47 @@ mptsas_dma_addr_destroy(ddi_dma_handle_t *dma_hdp, ddi_acc_handle_t *acc_hdp)
ddi_dma_free_handle(dma_hdp);
*dma_hdp = NULL;
}
+
+/*
+ * DDI UFM Callbacks
+ */
+static int
+mptsas_ufm_fill_image(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ ddi_ufm_image_t *img)
+{
+ if (imgno != 0)
+ return (EINVAL);
+
+ ddi_ufm_image_set_desc(img, "IOC Firmware");
+ ddi_ufm_image_set_nslots(img, 1);
+
+ return (0);
+}
+
+static int
+mptsas_ufm_fill_slot(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ uint_t slotno, ddi_ufm_slot_t *slot)
+{
+ mptsas_t *mpt = (mptsas_t *)arg;
+ char *buf;
+
+ if (imgno != 0 || slotno != 0 ||
+ ddi_prop_lookup_string(DDI_DEV_T_ANY, mpt->m_dip,
+ DDI_PROP_DONTPASS, "firmware-version", &buf) != DDI_PROP_SUCCESS)
+ return (EINVAL);
+
+ ddi_ufm_slot_set_attrs(slot, DDI_UFM_ATTR_ACTIVE);
+ ddi_ufm_slot_set_version(slot, buf);
+
+ ddi_prop_free(buf);
+
+ return (0);
+}
+
+static int
+mptsas_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/ufm.c b/usr/src/uts/common/io/ufm.c
new file mode 100644
index 0000000000..49a98c101a
--- /dev/null
+++ b/usr/src/uts/common/io/ufm.c
@@ -0,0 +1,482 @@
+/*
+ * 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 2019 Joyent, Inc.
+ */
+
+/*
+ * The ufm(7D) pseudo driver provides an ioctl interface for DDI UFM
+ * information. See ddi_ufm.h.
+ */
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/esunddi.h>
+#include <sys/ddi_ufm.h>
+#include <sys/ddi_ufm_impl.h>
+#include <sys/conf.h>
+#include <sys/debug.h>
+#include <sys/file.h>
+#include <sys/kmem.h>
+#include <sys/stat.h>
+
+#define UFMTEST_IOC ('u' << 24) | ('f' << 16) | ('t' << 8)
+#define UFMTEST_IOC_SETFW (UFMTEST_IOC | 1)
+
+static dev_info_t *ufm_devi = NULL;
+
+static int ufm_open(dev_t *, int, int, cred_t *);
+static int ufm_close(dev_t, int, int, cred_t *);
+static int ufm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
+
+static struct cb_ops ufm_cb_ops = {
+ .cb_open = ufm_open,
+ .cb_close = ufm_close,
+ .cb_strategy = nodev,
+ .cb_print = nodev,
+ .cb_dump = nodev,
+ .cb_read = nodev,
+ .cb_write = nodev,
+ .cb_ioctl = ufm_ioctl,
+ .cb_devmap = nodev,
+ .cb_mmap = nodev,
+ .cb_segmap = nodev,
+ .cb_chpoll = nochpoll,
+ .cb_prop_op = ddi_prop_op,
+ .cb_str = NULL,
+ .cb_flag = D_NEW | D_MP,
+ .cb_rev = CB_REV,
+ .cb_aread = nodev,
+ .cb_awrite = nodev
+};
+
+static int ufm_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
+static int ufm_attach(dev_info_t *, ddi_attach_cmd_t);
+static int ufm_detach(dev_info_t *, ddi_detach_cmd_t);
+
+static struct dev_ops ufm_ops = {
+ .devo_rev = DEVO_REV,
+ .devo_refcnt = 0,
+ .devo_getinfo = ufm_info,
+ .devo_identify = nulldev,
+ .devo_probe = nulldev,
+ .devo_attach = ufm_attach,
+ .devo_detach = ufm_detach,
+ .devo_reset = nodev,
+ .devo_cb_ops = &ufm_cb_ops,
+ .devo_bus_ops = NULL,
+ .devo_power = NULL,
+ .devo_quiesce = ddi_quiesce_not_needed
+};
+
+static struct modldrv modldrv = {
+ .drv_modops = &mod_driverops,
+ .drv_linkinfo = "Upgradeable FW Module driver",
+ .drv_dev_ops = &ufm_ops
+};
+
+static struct modlinkage modlinkage = {
+ .ml_rev = MODREV_1,
+ .ml_linkage = { (void *)&modldrv, NULL }
+};
+
+int
+_init(void)
+{
+ return (mod_install(&modlinkage));
+}
+
+int
+_fini(void)
+{
+ return (mod_remove(&modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+static int
+ufm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
+{
+ switch (infocmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ *result = ufm_devi;
+ return (DDI_SUCCESS);
+ case DDI_INFO_DEVT2INSTANCE:
+ *result = 0;
+ return (DDI_SUCCESS);
+ }
+ return (DDI_FAILURE);
+}
+
+static int
+ufm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
+{
+ if (cmd != DDI_ATTACH || ufm_devi != NULL)
+ return (DDI_FAILURE);
+
+ if (ddi_create_minor_node(devi, "ufm", S_IFCHR, 0, DDI_PSEUDO, 0) ==
+ DDI_FAILURE) {
+ ddi_remove_minor_node(devi, NULL);
+ return (DDI_FAILURE);
+ }
+
+ ufm_devi = devi;
+ return (DDI_SUCCESS);
+}
+
+static int
+ufm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
+{
+ if (cmd != DDI_DETACH)
+ return (DDI_FAILURE);
+
+ if (devi != NULL)
+ ddi_remove_minor_node(devi, NULL);
+
+ return (DDI_SUCCESS);
+}
+
+static int
+ufm_open(dev_t *devp, int flag, int otyp, cred_t *credp)
+{
+ const int inv_flags = FWRITE | FEXCL | FNDELAY | FNONBLOCK;
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ if (flag & inv_flags)
+ return (EINVAL);
+
+ if (drv_priv(credp) != 0)
+ return (EPERM);
+
+ return (0);
+}
+
+static int
+ufm_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+ return (0);
+}
+
+static boolean_t
+ufm_driver_ready(ddi_ufm_handle_t *ufmh)
+{
+ VERIFY(ufmh != NULL);
+
+ if (ufmh->ufmh_state & DDI_UFM_STATE_SHUTTING_DOWN ||
+ !(ufmh->ufmh_state & DDI_UFM_STATE_READY)) {
+ return (B_FALSE);
+ }
+ return (B_TRUE);
+}
+
+static int
+ufm_do_getcaps(intptr_t data, int mode)
+{
+ ddi_ufm_handle_t *ufmh;
+ ddi_ufm_cap_t caps;
+ ufm_ioc_getcaps_t ugc;
+ dev_info_t *dip;
+ int ret;
+ char devpath[MAXPATHLEN];
+
+ if (ddi_copyin((void *)data, &ugc, sizeof (ufm_ioc_getcaps_t),
+ mode) != 0)
+ return (EFAULT);
+
+ if (strlcpy(devpath, ugc.ufmg_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)) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (EAGAIN);
+ }
+
+ if (ugc.ufmg_version != ufmh->ufmh_version) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (ENOTSUP);
+ }
+
+ if ((ret = ufm_cache_fill(ufmh)) != 0) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (ret);
+ }
+
+ ret = ufmh->ufmh_ops->ddi_ufm_op_getcaps(ufmh, ufmh->ufmh_arg, &caps);
+ mutex_exit(&ufmh->ufmh_lock);
+ ddi_release_devi(dip);
+
+ if (ret != 0)
+ return (ret);
+
+ ugc.ufmg_caps = caps;
+
+ if (ddi_copyout(&ugc, (void *)data, sizeof (ufm_ioc_getcaps_t),
+ mode) != 0)
+ return (EFAULT);
+
+ return (0);
+}
+
+static int
+ufm_do_reportsz(intptr_t data, int mode)
+{
+ ddi_ufm_handle_t *ufmh;
+ dev_info_t *dip;
+ uint_t model;
+ size_t sz;
+ int ret;
+ char devpath[MAXPATHLEN];
+ ufm_ioc_bufsz_t ufbz;
+#ifdef _MULTI_DATAMODEL
+ ufm_ioc_bufsz32_t ufbz32;
+#endif
+
+ model = ddi_model_convert_from(mode);
+
+ switch (model) {
+#ifdef _MULTI_DATAMODEL
+ case DDI_MODEL_ILP32:
+ if (ddi_copyin((void *)data, &ufbz32,
+ sizeof (ufm_ioc_bufsz32_t), mode) != 0)
+ return (EFAULT);
+ ufbz.ufbz_version = ufbz32.ufbz_version;
+ if (strlcpy(ufbz.ufbz_devpath, ufbz32.ufbz_devpath,
+ MAXPATHLEN) >= MAXPATHLEN) {
+ return (EOVERFLOW);
+ }
+ break;
+#endif /* _MULTI_DATAMODEL */
+ case DDI_MODEL_NONE:
+ default:
+ if (ddi_copyin((void *)data, &ufbz,
+ sizeof (ufm_ioc_bufsz_t), mode) != 0)
+ return (EFAULT);
+ }
+
+ if (strlcpy(devpath, ufbz.ufbz_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)) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (EAGAIN);
+ }
+
+ if (ufbz.ufbz_version != ufmh->ufmh_version) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (ENOTSUP);
+ }
+
+ /*
+ * Note - ufm_cache_fill() also takes care of verifying that the driver
+ * supports the DDI_UFM_CAP_REPORT capability and will return non-zero,
+ * if not supported.
+ */
+ if ((ret = ufm_cache_fill(ufmh)) != 0) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (ret);
+ }
+ ddi_release_devi(dip);
+
+ ret = nvlist_size(ufmh->ufmh_report, &sz, NV_ENCODE_NATIVE);
+ mutex_exit(&ufmh->ufmh_lock);
+ if (ret != 0)
+ return (ret);
+
+ switch (model) {
+#ifdef _MULTI_DATAMODEL
+ case DDI_MODEL_ILP32:
+ ufbz32.ufbz_size = sz;
+ if (ddi_copyout(&ufbz32, (void *)data,
+ sizeof (ufm_ioc_bufsz32_t), mode) != 0)
+ return (EFAULT);
+ break;
+#endif /* _MULTI_DATAMODEL */
+ case DDI_MODEL_NONE:
+ default:
+ ufbz.ufbz_size = sz;
+ if (ddi_copyout(&ufbz, (void *)data,
+ sizeof (ufm_ioc_bufsz_t), mode) != 0)
+ return (EFAULT);
+ }
+ return (0);
+}
+
+static int
+ufm_do_report(intptr_t data, int mode)
+{
+ ddi_ufm_handle_t *ufmh;
+ uint_t model;
+ int ret = 0;
+ char *buf;
+ size_t sz;
+ dev_info_t *dip;
+ char devpath[MAXPATHLEN];
+ ufm_ioc_report_t ufmr;
+#ifdef _MULTI_DATAMODEL
+ ufm_ioc_report32_t ufmr32;
+#endif
+
+ model = ddi_model_convert_from(mode);
+
+ switch (model) {
+#ifdef _MULTI_DATAMODEL
+ case DDI_MODEL_ILP32:
+ if (ddi_copyin((void *)data, &ufmr32,
+ sizeof (ufm_ioc_report32_t), mode) != 0)
+ return (EFAULT);
+ ufmr.ufmr_version = ufmr32.ufmr_version;
+ if (strlcpy(ufmr.ufmr_devpath, ufmr32.ufmr_devpath,
+ MAXPATHLEN) >= MAXPATHLEN) {
+ return (EOVERFLOW);
+ }
+ ufmr.ufmr_bufsz = ufmr32.ufmr_bufsz;
+ ufmr.ufmr_buf = (caddr_t)(uintptr_t)ufmr32.ufmr_buf;
+ break;
+#endif /* _MULTI_DATAMODEL */
+ case DDI_MODEL_NONE:
+ default:
+ if (ddi_copyin((void *)data, &ufmr,
+ sizeof (ufm_ioc_report_t), mode) != 0)
+ return (EFAULT);
+ }
+
+ if (strlcpy(devpath, ufmr.ufmr_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)) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (EAGAIN);
+ }
+
+ if (ufmr.ufmr_version != ufmh->ufmh_version) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (ENOTSUP);
+ }
+
+ /*
+ * Note - ufm_cache_fill() also takes care of verifying that the driver
+ * supports the DDI_UFM_CAP_REPORT capability and will return non-zero,
+ * if not supported.
+ */
+ if ((ret = ufm_cache_fill(ufmh)) != 0) {
+ ddi_release_devi(dip);
+ mutex_exit(&ufmh->ufmh_lock);
+ return (ret);
+ }
+ ddi_release_devi(dip);
+
+ if ((ret = nvlist_size(ufmh->ufmh_report, &sz, NV_ENCODE_NATIVE)) !=
+ 0) {
+ mutex_exit(&ufmh->ufmh_lock);
+ return (ret);
+ }
+ if (sz > ufmr.ufmr_bufsz) {
+ mutex_exit(&ufmh->ufmh_lock);
+ return (EOVERFLOW);
+ }
+
+ buf = fnvlist_pack(ufmh->ufmh_report, &sz);
+ mutex_exit(&ufmh->ufmh_lock);
+
+ if (ddi_copyout(buf, ufmr.ufmr_buf, sz, mode) != 0) {
+ kmem_free(buf, sz);
+ return (EFAULT);
+ }
+ kmem_free(buf, sz);
+
+ switch (model) {
+#ifdef _MULTI_DATAMODEL
+ case DDI_MODEL_ILP32:
+ ufmr32.ufmr_bufsz = sz;
+ if (ddi_copyout(&ufmr32, (void *)data,
+ sizeof (ufm_ioc_report32_t), mode) != 0)
+ return (EFAULT);
+ break;
+#endif /* _MULTI_DATAMODEL */
+ case DDI_MODEL_NONE:
+ default:
+ ufmr.ufmr_bufsz = sz;
+ if (ddi_copyout(&ufmr, (void *)data,
+ sizeof (ufm_ioc_report_t), mode) != 0)
+ return (EFAULT);
+ }
+
+ return (0);
+}
+
+static int
+ufm_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp,
+ int *rvalp)
+{
+ int ret = 0;
+
+ if (drv_priv(credp) != 0)
+ return (EPERM);
+
+ switch (cmd) {
+ case UFM_IOC_GETCAPS:
+ ret = ufm_do_getcaps(data, mode);
+ break;
+
+ case UFM_IOC_REPORTSZ:
+ ret = ufm_do_reportsz(data, mode);
+ break;
+
+ case UFM_IOC_REPORT:
+ ret = ufm_do_report(data, mode);
+ break;
+ default:
+ return (ENOTTY);
+ }
+ return (ret);
+
+}
diff --git a/usr/src/uts/common/io/ufm.conf b/usr/src/uts/common/io/ufm.conf
new file mode 100644
index 0000000000..8e4a4a0a87
--- /dev/null
+++ b/usr/src/uts/common/io/ufm.conf
@@ -0,0 +1,16 @@
+#
+# 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 2019 Joyent, Inc.
+#
+
+name="ufm" parent="pseudo" instance=0;
diff --git a/usr/src/uts/common/io/ufmtest.c b/usr/src/uts/common/io/ufmtest.c
new file mode 100644
index 0000000000..ea9bb115d5
--- /dev/null
+++ b/usr/src/uts/common/io/ufmtest.c
@@ -0,0 +1,442 @@
+/*
+ * 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 2019 Joyent, Inc.
+ */
+
+/*
+ * This is a test driver used for exercising the DDI UFM subsystem.
+ *
+ * Most of the test cases depend on the ufmtest driver being loaded.
+ * On SmartOS, this driver will need to be manually installed, as it is not
+ * part of the platform image.
+ */
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/esunddi.h>
+#include <sys/ddi_ufm.h>
+#include <sys/conf.h>
+#include <sys/debug.h>
+#include <sys/file.h>
+#include <sys/kmem.h>
+#include <sys/stat.h>
+#include <sys/zone.h>
+
+#include "ufmtest.h"
+
+typedef struct ufmtest {
+ dev_info_t *ufmt_devi;
+ nvlist_t *ufmt_nvl;
+ ddi_ufm_handle_t *ufmt_ufmh;
+ uint32_t ufmt_failflags;
+} ufmtest_t;
+
+static ufmtest_t ufmt = { 0 };
+
+static int ufmtest_open(dev_t *, int, int, cred_t *);
+static int ufmtest_close(dev_t, int, int, cred_t *);
+static int ufmtest_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
+
+static struct cb_ops ufmtest_cb_ops = {
+ .cb_open = ufmtest_open,
+ .cb_close = ufmtest_close,
+ .cb_strategy = nodev,
+ .cb_print = nodev,
+ .cb_dump = nodev,
+ .cb_read = nodev,
+ .cb_write = nodev,
+ .cb_ioctl = ufmtest_ioctl,
+ .cb_devmap = nodev,
+ .cb_mmap = nodev,
+ .cb_segmap = nodev,
+ .cb_chpoll = nochpoll,
+ .cb_prop_op = ddi_prop_op,
+ .cb_str = NULL,
+ .cb_flag = D_NEW | D_MP,
+ .cb_rev = CB_REV,
+ .cb_aread = nodev,
+ .cb_awrite = nodev
+};
+
+static int ufmtest_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
+static int ufmtest_attach(dev_info_t *, ddi_attach_cmd_t);
+static int ufmtest_detach(dev_info_t *, ddi_detach_cmd_t);
+
+static struct dev_ops ufmtest_ops = {
+ .devo_rev = DEVO_REV,
+ .devo_refcnt = 0,
+ .devo_getinfo = ufmtest_info,
+ .devo_identify = nulldev,
+ .devo_probe = nulldev,
+ .devo_attach = ufmtest_attach,
+ .devo_detach = ufmtest_detach,
+ .devo_reset = nodev,
+ .devo_cb_ops = &ufmtest_cb_ops,
+ .devo_bus_ops = NULL,
+ .devo_power = NULL,
+ .devo_quiesce = ddi_quiesce_not_needed
+};
+
+static struct modldrv modldrv = {
+ .drv_modops = &mod_driverops,
+ .drv_linkinfo = "DDI UFM test driver",
+ .drv_dev_ops = &ufmtest_ops
+};
+
+static struct modlinkage modlinkage = {
+ .ml_rev = MODREV_1,
+ .ml_linkage = { (void *)&modldrv, NULL }
+};
+
+static int ufmtest_nimages(ddi_ufm_handle_t *, void *, uint_t *);
+static int ufmtest_fill_image(ddi_ufm_handle_t *, void *, uint_t,
+ ddi_ufm_image_t *);
+static int ufmtest_fill_slot(ddi_ufm_handle_t *, void *, uint_t, uint_t,
+ ddi_ufm_slot_t *);
+static int ufmtest_getcaps(ddi_ufm_handle_t *, void *, ddi_ufm_cap_t *);
+
+static ddi_ufm_ops_t ufmtest_ufm_ops = {
+ ufmtest_nimages,
+ ufmtest_fill_image,
+ ufmtest_fill_slot,
+ ufmtest_getcaps
+};
+
+
+int
+_init(void)
+{
+ return (mod_install(&modlinkage));
+}
+
+int
+_fini(void)
+{
+ return (mod_remove(&modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+static int
+ufmtest_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
+{
+ switch (infocmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ *result = ufmt.ufmt_devi;
+ return (DDI_SUCCESS);
+ case DDI_INFO_DEVT2INSTANCE:
+ *result = (void *)(uintptr_t)ddi_get_instance(dip);
+ return (DDI_SUCCESS);
+ }
+ return (DDI_FAILURE);
+}
+
+static int
+ufmtest_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
+{
+ if (cmd != DDI_ATTACH || ufmt.ufmt_devi != NULL)
+ return (DDI_FAILURE);
+
+ if (ddi_create_minor_node(devi, "ufmtest", S_IFCHR, 0, DDI_PSEUDO,
+ 0) == DDI_FAILURE) {
+ ddi_remove_minor_node(devi, NULL);
+ return (DDI_FAILURE);
+ }
+
+ ufmt.ufmt_devi = devi;
+
+ if (ddi_ufm_init(ufmt.ufmt_devi, DDI_UFM_CURRENT_VERSION,
+ &ufmtest_ufm_ops, &ufmt.ufmt_ufmh, NULL) != 0) {
+ dev_err(ufmt.ufmt_devi, CE_WARN, "failed to initialize UFM "
+ "subsystem");
+ return (DDI_FAILURE);
+ }
+
+ return (DDI_SUCCESS);
+}
+
+static int
+ufmtest_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
+{
+ if (cmd != DDI_DETACH)
+ return (DDI_FAILURE);
+
+ if (devi != NULL)
+ ddi_remove_minor_node(devi, NULL);
+
+ ddi_ufm_fini(ufmt.ufmt_ufmh);
+ if (ufmt.ufmt_nvl != NULL) {
+ nvlist_free(ufmt.ufmt_nvl);
+ ufmt.ufmt_nvl = NULL;
+ }
+
+ return (DDI_SUCCESS);
+}
+
+static int
+ufmtest_open(dev_t *devp, int flag, int otyp, cred_t *credp)
+{
+ const int inv_flags = FWRITE | FEXCL | FNDELAY | FNONBLOCK;
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ if (flag & inv_flags)
+ return (EINVAL);
+
+ if (drv_priv(credp) != 0)
+ return (EPERM);
+
+ if (getzoneid() != GLOBAL_ZONEID)
+ return (EPERM);
+
+ return (0);
+}
+
+static int
+ufmtest_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+ return (0);
+}
+
+/*
+ * By default, this pseudo test driver contains no hardcoded UFM data to
+ * report. This ioctl takes a packed nvlist, representing a UFM report.
+ * This data is then used as a source for firmware information by this
+ * driver when it's UFM callback are called.
+ *
+ * External test programs can use this ioctl to effectively seed this
+ * driver with arbitrary firmware information which it will report up to the
+ * DDI UFM subsystem.
+ */
+static int
+ufmtest_do_setfw(intptr_t data, int mode)
+{
+ int ret;
+ uint_t model;
+ ufmtest_ioc_setfw_t setfw;
+ char *nvlbuf = NULL;
+#ifdef _MULTI_DATAMODEL
+ ufmtest_ioc_setfw32_t setfw32;
+#endif
+ model = ddi_model_convert_from(mode);
+
+ switch (model) {
+#ifdef _MULTI_DATAMODEL
+ case DDI_MODEL_ILP32:
+ if (ddi_copyin((void *)data, &setfw32,
+ sizeof (ufmtest_ioc_setfw32_t), mode) != 0)
+ return (EFAULT);
+ setfw.utsw_bufsz = setfw32.utsw_bufsz;
+ setfw.utsw_buf = (caddr_t)(uintptr_t)setfw32.utsw_buf;
+ break;
+#endif /* _MULTI_DATAMODEL */
+ case DDI_MODEL_NONE:
+ default:
+ if (ddi_copyin((void *)data, &setfw,
+ sizeof (ufmtest_ioc_setfw_t), mode) != 0)
+ return (EFAULT);
+ }
+
+ if (ufmt.ufmt_nvl != NULL) {
+ nvlist_free(ufmt.ufmt_nvl);
+ ufmt.ufmt_nvl = NULL;
+ }
+
+ nvlbuf = kmem_zalloc(setfw.utsw_bufsz, KM_NOSLEEP | KM_NORMALPRI);
+ if (nvlbuf == NULL)
+ return (ENOMEM);
+
+ if (ddi_copyin(setfw.utsw_buf, nvlbuf, setfw.utsw_bufsz, mode) != 0) {
+ kmem_free(nvlbuf, setfw.utsw_bufsz);
+ return (EFAULT);
+ }
+
+ ret = nvlist_unpack(nvlbuf, setfw.utsw_bufsz, &ufmt.ufmt_nvl,
+ NV_ENCODE_NATIVE);
+ kmem_free(nvlbuf, setfw.utsw_bufsz);
+
+ if (ret != 0)
+ return (ret);
+
+ /*
+ * Notify the UFM subsystem that our firmware information has changed.
+ */
+ ddi_ufm_update(ufmt.ufmt_ufmh);
+
+ return (0);
+}
+
+static int
+ufmtest_do_toggle_fails(intptr_t data, int mode)
+{
+ ufmtest_ioc_fails_t fails;
+
+ if (ddi_copyin((void *)data, &fails, sizeof (ufmtest_ioc_fails_t),
+ mode) != 0)
+ return (EFAULT);
+
+ if (fails.utfa_flags > UFMTEST_MAX_FAILFLAGS)
+ return (EINVAL);
+
+ ufmt.ufmt_failflags = fails.utfa_flags;
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+ufmtest_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp,
+ int *rvalp)
+{
+ int ret = 0;
+
+ if (drv_priv(credp) != 0)
+ return (EPERM);
+
+ switch (cmd) {
+ case UFMTEST_IOC_SET_FW:
+ ret = ufmtest_do_setfw(data, mode);
+ break;
+ case UFMTEST_IOC_TOGGLE_FAILS:
+ ret = ufmtest_do_toggle_fails(data, mode);
+ break;
+ case UFMTEST_IOC_DO_UPDATE:
+ ddi_ufm_update(ufmt.ufmt_ufmh);
+ break;
+ default:
+ return (ENOTTY);
+ }
+ return (ret);
+}
+
+static int
+ufmtest_nimages(ddi_ufm_handle_t *ufmh, void *arg, uint_t *nimgs)
+{
+ nvlist_t **imgs;
+ uint_t ni;
+
+ if (ufmt.ufmt_failflags & UFMTEST_FAIL_NIMAGES ||
+ ufmt.ufmt_nvl == NULL)
+ return (EINVAL);
+
+ if (nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES, &imgs,
+ &ni) != 0)
+ return (EINVAL);
+
+ *nimgs = ni;
+ return (0);
+}
+
+static int
+ufmtest_fill_image(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ ddi_ufm_image_t *img)
+{
+ nvlist_t **images, *misc, *miscdup = NULL, **slots;
+ char *desc;
+ uint_t ni, ns;
+
+ if (ufmt.ufmt_failflags & UFMTEST_FAIL_FILLIMAGE ||
+ ufmt.ufmt_nvl == NULL ||
+ nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES,
+ &images, &ni) != 0)
+ goto err;
+
+ if (imgno >= ni)
+ goto err;
+
+ if (nvlist_lookup_string(images[imgno], DDI_UFM_NV_IMAGE_DESC,
+ &desc) != 0 ||
+ nvlist_lookup_nvlist_array(images[imgno], DDI_UFM_NV_IMAGE_SLOTS,
+ &slots, &ns) != 0)
+ goto err;
+
+ ddi_ufm_image_set_desc(img, desc);
+ ddi_ufm_image_set_nslots(img, ns);
+
+ if (nvlist_lookup_nvlist(images[imgno], DDI_UFM_NV_IMAGE_MISC, &misc)
+ == 0) {
+ if (nvlist_dup(misc, &miscdup, 0) != 0)
+ return (ENOMEM);
+
+ ddi_ufm_image_set_misc(img, miscdup);
+ }
+ return (0);
+err:
+ return (EINVAL);
+}
+
+static int
+ufmtest_fill_slot(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno,
+ uint_t slotno, ddi_ufm_slot_t *slot)
+{
+ nvlist_t **images, *misc, *miscdup = NULL, **slots;
+ char *vers;
+ uint32_t attrs;
+ uint_t ni, ns;
+
+ if (ufmt.ufmt_failflags & UFMTEST_FAIL_FILLSLOT ||
+ ufmt.ufmt_nvl == NULL ||
+ nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES,
+ &images, &ni) != 0)
+ goto err;
+
+ if (imgno >= ni)
+ goto err;
+
+ if (nvlist_lookup_nvlist_array(images[imgno], DDI_UFM_NV_IMAGE_SLOTS,
+ &slots, &ns) != 0)
+ goto err;
+
+ if (slotno >= ns)
+ goto err;
+
+ if (nvlist_lookup_uint32(slots[slotno], DDI_UFM_NV_SLOT_ATTR,
+ &attrs) != 0)
+ goto err;
+
+ ddi_ufm_slot_set_attrs(slot, attrs);
+ if (attrs & DDI_UFM_ATTR_EMPTY)
+ return (0);
+
+ if (nvlist_lookup_string(slots[slotno], DDI_UFM_NV_SLOT_VERSION,
+ &vers) != 0)
+ goto err;
+
+ ddi_ufm_slot_set_version(slot, vers);
+
+ if (nvlist_lookup_nvlist(slots[slotno], DDI_UFM_NV_SLOT_MISC, &misc) ==
+ 0) {
+ if (nvlist_dup(misc, &miscdup, 0) != 0)
+ return (ENOMEM);
+
+ ddi_ufm_slot_set_misc(slot, miscdup);
+ }
+ return (0);
+err:
+ return (EINVAL);
+}
+
+static int
+ufmtest_getcaps(ddi_ufm_handle_t *ufmh, void *arg, ddi_ufm_cap_t *caps)
+{
+ if (ufmt.ufmt_failflags & UFMTEST_FAIL_GETCAPS)
+ return (EINVAL);
+
+ *caps = DDI_UFM_CAP_REPORT;
+
+ return (0);
+}
diff --git a/usr/src/uts/common/io/ufmtest.conf b/usr/src/uts/common/io/ufmtest.conf
new file mode 100644
index 0000000000..cc813eb744
--- /dev/null
+++ b/usr/src/uts/common/io/ufmtest.conf
@@ -0,0 +1,16 @@
+#
+# 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 2019 Joyent, Inc.
+#
+
+name="ufmtest" parent="pseudo" instance=0;
diff --git a/usr/src/uts/common/io/ufmtest.h b/usr/src/uts/common/io/ufmtest.h
new file mode 100644
index 0000000000..58787f569a
--- /dev/null
+++ b/usr/src/uts/common/io/ufmtest.h
@@ -0,0 +1,75 @@
+/*
+ * 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 2019 Joyent, Inc.
+ */
+
+#ifndef _UFMTEST_H
+#define _UFMTEST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _KERNEL
+#include <sys/cred.h>
+#include <sys/dditypes.h>
+#include <sys/nvpair.h>
+#include <sys/param.h>
+#else
+#include <sys/nvpair.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#endif /* _KERNEL */
+
+#define DDI_UFMTEST_DEV "/dev/ufmtest"
+
+#define UFMTEST_IOC ('u' << 24) | ('f' << 16) | ('t' << 8)
+#define UFMTEST_IOC_SET_FW (UFMTEST_IOC | 1)
+#define UFMTEST_IOC_TOGGLE_FAILS (UFMTEST_IOC | 2)
+#define UFMTEST_IOC_DO_UPDATE (UFMTEST_IOC | 3)
+
+typedef struct ufmtest_ioc_setfw {
+ size_t utsw_bufsz;
+ caddr_t utsw_buf;
+} ufmtest_ioc_setfw_t;
+
+#ifdef _KERNEL
+typedef struct ufmtest_ioc_setfw32 {
+ size32_t utsw_bufsz;
+ caddr32_t utsw_buf;
+} ufmtest_ioc_setfw32_t;
+#endif /* _KERNEL */
+
+/*
+ * The argument for the UFMTEST_IOC_TOGGLE_FAILS ioctl is a bitfield
+ * indicating which of the UFM entry points we want to simulate a failure on.
+ */
+typedef enum {
+ UFMTEST_FAIL_GETCAPS = 1 << 0,
+ UFMTEST_FAIL_NIMAGES = 1 << 1,
+ UFMTEST_FAIL_FILLIMAGE = 1 << 2,
+ UFMTEST_FAIL_FILLSLOT = 1 << 3
+} ufmtest_failflags_t;
+
+#define UFMTEST_MAX_FAILFLAGS (UFMTEST_FAIL_GETCAPS | UFMTEST_FAIL_NIMAGES | \
+ UFMTEST_FAIL_FILLIMAGE | UFMTEST_FAIL_FILLSLOT)
+
+typedef struct ufmtest_ioc_fails {
+ ufmtest_failflags_t utfa_flags;
+} ufmtest_ioc_fails_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UFMTEST_H */
diff --git a/usr/src/uts/common/mapfiles/ddi.mapfile b/usr/src/uts/common/mapfiles/ddi.mapfile
index 5a124766e2..b68a53e3cf 100644
--- a/usr/src/uts/common/mapfiles/ddi.mapfile
+++ b/usr/src/uts/common/mapfiles/ddi.mapfile
@@ -10,7 +10,7 @@
#
#
-# Copyright 2016 Joyent, Inc.
+# Copyright 2019 Joyent, Inc.
#
#
@@ -101,6 +101,7 @@ SYMBOL_SCOPE {
ddi_prop_free { FLAGS = EXTERN };
ddi_prop_get_int { FLAGS = EXTERN };
ddi_prop_lookup_int_array { FLAGS = EXTERN };
+ ddi_prop_lookup_string { FLAGS = EXTERN };
ddi_prop_op { FLAGS = EXTERN };
ddi_prop_remove_all { FLAGS = EXTERN };
ddi_prop_update_int_array { FLAGS = EXTERN };
@@ -116,6 +117,15 @@ SYMBOL_SCOPE {
ddi_taskq_create { FLAGS = EXTERN };
ddi_taskq_destroy { FLAGS = EXTERN };
ddi_taskq_dispatch { FLAGS = EXTERN };
+ ddi_ufm_fini { FLAGS = EXTERN };
+ ddi_ufm_image_set_desc { FLAGS = EXTERN };
+ ddi_ufm_image_set_misc { FLAGS = EXTERN };
+ ddi_ufm_image_set_nslots { FLAGS = EXTERN };
+ ddi_ufm_init { FLAGS = EXTERN };
+ ddi_ufm_slot_set_attrs { FLAGS = EXTERN };
+ ddi_ufm_slot_set_misc { FLAGS = EXTERN };
+ ddi_ufm_slot_set_version { FLAGS = EXTERN };
+ ddi_ufm_update { FLAGS = EXTERN };
delay { FLAGS = EXTERN };
desballoc { FLAGS = EXTERN };
dev_err { FLAGS = EXTERN };
@@ -158,6 +168,9 @@ SYMBOL_SCOPE {
nochpoll { FLAGS = EXTERN };
nodev { FLAGS = EXTERN };
nulldev { FLAGS = EXTERN };
+ nvlist_add_string { FLAGS = EXTERN };
+ nvlist_alloc { FLAGS = EXTERN };
+ nvlist_free { FLAGS = EXTERN };
panic { FLAGS = EXTERN };
pci_config_get16 { FLAGS = EXTERN };
pci_config_get32 { FLAGS = EXTERN };
diff --git a/usr/src/uts/common/os/autoconf.c b/usr/src/uts/common/os/autoconf.c
index 229c250a64..71af31ba2b 100644
--- a/usr/src/uts/common/os/autoconf.c
+++ b/usr/src/uts/common/os/autoconf.c
@@ -22,6 +22,9 @@
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
+/*
+ * Copyright 2019 Joyent, Inc.
+ */
/*
* This file contains ddi functions needed during boot and DR.
@@ -49,6 +52,7 @@
#include <sys/bootconf.h>
#include <sys/fm/util.h>
#include <sys/ddifm_impl.h>
+#include <sys/ddi_ufm_impl.h>
extern dev_info_t *top_devinfo;
extern dev_info_t *scsi_vhci_dip;
@@ -91,6 +95,7 @@ setup_ddi(void)
fm_init();
ndi_fm_init();
irm_init();
+ ufm_init();
(void) i_ddi_load_drvconf(DDI_MAJOR_T_NONE);
diff --git a/usr/src/uts/common/os/ddi_ufm.c b/usr/src/uts/common/os/ddi_ufm.c
new file mode 100644
index 0000000000..c115bc4df5
--- /dev/null
+++ b/usr/src/uts/common/os/ddi_ufm.c
@@ -0,0 +1,452 @@
+/*
+ * 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 2019 Joyent, Inc.
+ */
+
+#include <sys/avl.h>
+#include <sys/ddi_ufm.h>
+#include <sys/ddi_ufm_impl.h>
+#include <sys/debug.h>
+#include <sys/kmem.h>
+#include <sys/sunddi.h>
+#include <sys/stddef.h>
+
+/*
+ * The UFM subsystem tracks its internal state with respect to device
+ * drivers that participate in the DDI UFM subsystem on a per-instance basis
+ * via ddi_ufm_handle_t structures (see ddi_ufm_impl.h). This is known as the
+ * UFM handle. The UFM handle contains a pointer to the driver's UFM ops,
+ * which the ufm(7D) pseudo driver uses to invoke the UFM entry points in
+ * response to DDI UFM ioctls. Additionally, the DDI UFM subsystem uses the
+ * handle to maintain cached UFM image and slot data.
+ *
+ * In order to track and provide fast lookups of a driver instance's UFM
+ * handle, the DDI UFM subsystem stores a pointer to the handle in a global AVL
+ * tree. UFM handles are added to the tree when a driver calls ddi_ufm_init(9E)
+ * and removed from the tree when a driver calls ddi_ufm_fini(9E).
+ *
+ * Some notes on the locking strategy/rules.
+ *
+ * All access to the tree is serialized via the mutex, ufm_lock.
+ * Additionally, each UFM handle is protected by a per-handle mutex.
+ *
+ * Code must acquire ufm_lock in order to walk the tree. Before reading or
+ * modifying the state of any UFM handle, code must then acquire the
+ * UFM handle lock. Once the UFM handle lock has been acquired, ufm_lock
+ * should be dropped.
+ *
+ * Only one UFM handle lock should be held at any time.
+ * If a UFM handle lock is held, it must be released before attempting to
+ * re-acquire ufm_lock.
+ *
+ * For example, the lock sequence for calling a UFM entry point and/or
+ * reading/modifying UFM handle state would be as follows:
+ * - acquire ufm_lock
+ * - walk tree to find UFH handle
+ * - acquire UFM handle lock
+ * - release ufm_lock
+ * - call entry point and/or access handle state
+ *
+ * Testing
+ * -------
+ * A set of automated tests for the DDI UFM subsystem exists at:
+ * usr/src/test/os-tests/tests/ddi_ufm/
+ *
+ * These tests should be run whenever changes are made to the DDI UFM
+ * subsystem or the ufm driver.
+ */
+static avl_tree_t ufm_handles;
+static kmutex_t ufm_lock;
+
+static int ufm_handle_compare(const void *, const void *);
+
+static void
+ufm_cache_invalidate(ddi_ufm_handle_t *ufmh)
+{
+ ASSERT(MUTEX_HELD(&ufmh->ufmh_lock));
+
+ if (ufmh->ufmh_images == NULL)
+ return;
+
+ for (uint_t i = 0; i < ufmh->ufmh_nimages; i++) {
+ struct ddi_ufm_image *img = &ufmh->ufmh_images[i];
+
+ if (img->ufmi_slots == NULL)
+ continue;
+
+ for (uint_t s = 0; s < img->ufmi_nslots; s++) {
+ struct ddi_ufm_slot *slot = &img->ufmi_slots[s];
+
+ if (slot->ufms_version != NULL)
+ strfree(slot->ufms_version);
+ nvlist_free(slot->ufms_misc);
+ }
+ kmem_free(img->ufmi_slots,
+ (img->ufmi_nslots * sizeof (ddi_ufm_slot_t)));
+ if (img->ufmi_desc != NULL)
+ strfree(img->ufmi_desc);
+ nvlist_free(img->ufmi_misc);
+ }
+
+ kmem_free(ufmh->ufmh_images,
+ (ufmh->ufmh_nimages * sizeof (ddi_ufm_image_t)));
+ ufmh->ufmh_images = NULL;
+ ufmh->ufmh_nimages = 0;
+ ufmh->ufmh_caps = 0;
+ nvlist_free(ufmh->ufmh_report);
+ ufmh->ufmh_report = NULL;
+}
+
+static void
+free_nvlist_array(nvlist_t **nvlarr, uint_t nelems)
+{
+ for (uint_t i = 0; i < nelems; i++) {
+ if (nvlarr[i] != NULL)
+ nvlist_free(nvlarr[i]);
+ }
+ kmem_free(nvlarr, nelems * sizeof (nvlist_t *));
+}
+
+int
+ufm_cache_fill(ddi_ufm_handle_t *ufmh)
+{
+ int ret;
+ uint_t nimgs;
+ ddi_ufm_cap_t caps;
+ nvlist_t **images = NULL, **slots = NULL;
+
+ ASSERT(MUTEX_HELD(&ufmh->ufmh_lock));
+
+ /*
+ * Check whether we already have a cached report and if so, return
+ * straight away.
+ */
+ if (ufmh->ufmh_report != NULL)
+ return (0);
+
+ /*
+ * First check which UFM caps this driver supports. If it doesn't
+ * support DDI_UFM_CAP_REPORT, then there's nothing to cache and we
+ * can just return.
+ */
+ ret = ufmh->ufmh_ops->ddi_ufm_op_getcaps(ufmh, ufmh->ufmh_arg, &caps);
+ if (ret != 0)
+ return (ret);
+
+ ufmh->ufmh_caps = caps;
+ if ((ufmh->ufmh_caps & DDI_UFM_CAP_REPORT) == 0)
+ return (ENOTSUP);
+
+ /*
+ * Next, figure out how many UFM images the device has. If a
+ * ddi_ufm_op_nimages entry point wasn't specified, then we assume
+ * that the device has a single image.
+ */
+ if (ufmh->ufmh_ops->ddi_ufm_op_nimages != NULL) {
+ ret = ufmh->ufmh_ops->ddi_ufm_op_nimages(ufmh, ufmh->ufmh_arg,
+ &nimgs);
+ if (ret == 0 && nimgs > 0)
+ ufmh->ufmh_nimages = nimgs;
+ else
+ goto cache_fail;
+ } else {
+ ufmh->ufmh_nimages = 1;
+ }
+
+ /*
+ * Now that we know how many images we're dealing with, allocate space
+ * for an appropriately-sized array of ddi_ufm_image_t structs and then
+ * iterate through them calling the ddi_ufm_op_fill_image entry point
+ * so that the driver can fill them in.
+ */
+ ufmh->ufmh_images =
+ kmem_zalloc((sizeof (ddi_ufm_image_t) * ufmh->ufmh_nimages),
+ KM_NOSLEEP | KM_NORMALPRI);
+ if (ufmh->ufmh_images == NULL)
+ return (ENOMEM);
+
+ for (uint_t i = 0; i < ufmh->ufmh_nimages; i++) {
+ struct ddi_ufm_image *img = &ufmh->ufmh_images[i];
+
+ ret = ufmh->ufmh_ops->ddi_ufm_op_fill_image(ufmh,
+ ufmh->ufmh_arg, i, img);
+
+ if (ret != 0)
+ goto cache_fail;
+
+ ASSERT(img->ufmi_desc != NULL && img->ufmi_nslots != 0);
+
+ img->ufmi_slots =
+ kmem_zalloc((sizeof (ddi_ufm_slot_t) * img->ufmi_nslots),
+ KM_NOSLEEP | KM_NORMALPRI);
+ if (img->ufmi_slots == NULL) {
+ ret = ENOMEM;
+ goto cache_fail;
+ }
+
+ for (uint_t s = 0; s < img->ufmi_nslots; s++) {
+ struct ddi_ufm_slot *slot = &img->ufmi_slots[s];
+
+ ret = ufmh->ufmh_ops->ddi_ufm_op_fill_slot(ufmh,
+ ufmh->ufmh_arg, i, s, slot);
+
+ if (ret != 0)
+ goto cache_fail;
+
+ ASSERT(slot->ufms_attrs & DDI_UFM_ATTR_EMPTY ||
+ slot->ufms_version != NULL);
+ }
+ }
+ images = kmem_zalloc(sizeof (nvlist_t *) * ufmh->ufmh_nimages,
+ KM_SLEEP);
+ for (uint_t i = 0; i < ufmh->ufmh_nimages; i ++) {
+ ddi_ufm_image_t *img = &ufmh->ufmh_images[i];
+
+ images[i] = fnvlist_alloc();
+ fnvlist_add_string(images[i], DDI_UFM_NV_IMAGE_DESC,
+ img->ufmi_desc);
+ if (img->ufmi_misc != NULL) {
+ fnvlist_add_nvlist(images[i], DDI_UFM_NV_IMAGE_MISC,
+ img->ufmi_misc);
+ }
+
+ slots = kmem_zalloc(sizeof (nvlist_t *) * img->ufmi_nslots,
+ KM_SLEEP);
+ for (uint_t s = 0; s < img->ufmi_nslots; s++) {
+ ddi_ufm_slot_t *slot = &img->ufmi_slots[s];
+
+ slots[s] = fnvlist_alloc();
+ fnvlist_add_uint32(slots[s], DDI_UFM_NV_SLOT_ATTR,
+ slot->ufms_attrs);
+ if (slot->ufms_attrs & DDI_UFM_ATTR_EMPTY)
+ continue;
+
+ fnvlist_add_string(slots[s], DDI_UFM_NV_SLOT_VERSION,
+ slot->ufms_version);
+ if (slot->ufms_misc != NULL) {
+ fnvlist_add_nvlist(slots[s],
+ DDI_UFM_NV_SLOT_MISC, slot->ufms_misc);
+ }
+ }
+ fnvlist_add_nvlist_array(images[i], DDI_UFM_NV_IMAGE_SLOTS,
+ slots, img->ufmi_nslots);
+ free_nvlist_array(slots, img->ufmi_nslots);
+ }
+ ufmh->ufmh_report = fnvlist_alloc();
+ fnvlist_add_nvlist_array(ufmh->ufmh_report, DDI_UFM_NV_IMAGES, images,
+ ufmh->ufmh_nimages);
+ free_nvlist_array(images, ufmh->ufmh_nimages);
+
+ return (0);
+
+cache_fail:
+ ufm_cache_invalidate(ufmh);
+ return (ret);
+}
+
+/*
+ * This gets called early in boot by setup_ddi().
+ */
+void
+ufm_init(void)
+{
+ mutex_init(&ufm_lock, NULL, MUTEX_DEFAULT, NULL);
+
+ avl_create(&ufm_handles, ufm_handle_compare,
+ sizeof (ddi_ufm_handle_t),
+ offsetof(ddi_ufm_handle_t, ufmh_link));
+}
+
+static int
+ufm_handle_compare(const void *a1, const void *a2)
+{
+ const struct ddi_ufm_handle *hdl1, *hdl2;
+ int cmp;
+
+ hdl1 = (struct ddi_ufm_handle *)a1;
+ hdl2 = (struct ddi_ufm_handle *)a2;
+
+ cmp = strcmp(hdl1->ufmh_devpath, hdl2->ufmh_devpath);
+
+ if (cmp > 0)
+ return (1);
+ else if (cmp < 0)
+ return (-1);
+ else
+ return (0);
+}
+
+/*
+ * This is used by the ufm driver to lookup the UFM handle associated with a
+ * particular devpath.
+ *
+ * On success, this function returns the reqested UFH handle, with its lock
+ * held. Caller is responsible to dropping the lock when it is done with the
+ * handle.
+ */
+struct ddi_ufm_handle *
+ufm_find(const char *devpath)
+{
+ struct ddi_ufm_handle find = { 0 }, *ufmh;
+
+ (void) strlcpy(find.ufmh_devpath, devpath, MAXPATHLEN);
+
+ mutex_enter(&ufm_lock);
+ ufmh = avl_find(&ufm_handles, &find, NULL);
+ if (ufmh != NULL)
+ mutex_enter(&ufmh->ufmh_lock);
+ mutex_exit(&ufm_lock);
+
+ return (ufmh);
+}
+
+int
+ddi_ufm_init(dev_info_t *dip, uint_t version, ddi_ufm_ops_t *ufmops,
+ ddi_ufm_handle_t **ufmh, void *arg)
+{
+ ddi_ufm_handle_t *old_ufmh;
+ char devpath[MAXPATHLEN];
+
+ VERIFY(version != 0 && ufmops != NULL);
+ VERIFY(ufmops->ddi_ufm_op_fill_image != NULL &&
+ ufmops->ddi_ufm_op_fill_slot != NULL &&
+ ufmops->ddi_ufm_op_getcaps != NULL);
+
+ if (version < DDI_UFM_VERSION_ONE || version > DDI_UFM_CURRENT_VERSION)
+ return (ENOTSUP);
+
+ /*
+ * First we check if we already have a UFM handle for this device
+ * instance. This can happen if the module got unloaded or the driver
+ * was suspended after previously registering with the UFM subsystem.
+ *
+ * If we find an old handle then we simply reset its state and hand it
+ * back to the driver.
+ *
+ * If we don't find an old handle then this is a new registration, so
+ * we allocate and initialize a new handle.
+ *
+ * In either case, we don't need to NULL-out the other fields (like
+ * ufmh_report) as in order for them to be referenced, ufmh_state has to
+ * first transition to DDI_UFM_STATE_READY. The only way that can
+ * happen is for the driver to call ddi_ufm_update(), which will call
+ * ufm_cache_invalidate(), which in turn will take care of properly
+ * cleaning up and reinitializing the other fields in the handle.
+ */
+ (void) ddi_pathname(dip, devpath);
+ if ((old_ufmh = ufm_find(devpath)) != NULL) {
+ *ufmh = old_ufmh;
+ } else {
+ *ufmh = kmem_zalloc(sizeof (ddi_ufm_handle_t), KM_SLEEP);
+ (void) strlcpy((*ufmh)->ufmh_devpath, devpath, MAXPATHLEN);
+ mutex_init(&(*ufmh)->ufmh_lock, NULL, MUTEX_DEFAULT, NULL);
+ }
+ (*ufmh)->ufmh_ops = ufmops;
+ (*ufmh)->ufmh_arg = arg;
+ (*ufmh)->ufmh_version = version;
+ (*ufmh)->ufmh_state = DDI_UFM_STATE_INIT;
+
+ /*
+ * If this is a new registration, add the UFM handle to the global AVL
+ * tree of handles.
+ *
+ * Otherwise, if it's an old registration then ufm_find() will have
+ * returned the old handle with the lock already held, so we need to
+ * release it before returning.
+ */
+ if (old_ufmh == NULL) {
+ mutex_enter(&ufm_lock);
+ avl_add(&ufm_handles, *ufmh);
+ mutex_exit(&ufm_lock);
+ } else {
+ mutex_exit(&old_ufmh->ufmh_lock);
+ }
+
+ return (DDI_SUCCESS);
+}
+
+void
+ddi_ufm_fini(ddi_ufm_handle_t *ufmh)
+{
+ VERIFY(ufmh != NULL);
+
+ mutex_enter(&ufmh->ufmh_lock);
+ ufmh->ufmh_state |= DDI_UFM_STATE_SHUTTING_DOWN;
+ ufm_cache_invalidate(ufmh);
+ mutex_exit(&ufmh->ufmh_lock);
+}
+
+void
+ddi_ufm_update(ddi_ufm_handle_t *ufmh)
+{
+ VERIFY(ufmh != NULL);
+
+ mutex_enter(&ufmh->ufmh_lock);
+ if (ufmh->ufmh_state & DDI_UFM_STATE_SHUTTING_DOWN) {
+ mutex_exit(&ufmh->ufmh_lock);
+ return;
+ }
+ ufm_cache_invalidate(ufmh);
+ ufmh->ufmh_state |= DDI_UFM_STATE_READY;
+ mutex_exit(&ufmh->ufmh_lock);
+}
+
+void
+ddi_ufm_image_set_desc(ddi_ufm_image_t *uip, const char *desc)
+{
+ VERIFY(uip != NULL && desc != NULL);
+ if (uip->ufmi_desc != NULL)
+ strfree(uip->ufmi_desc);
+
+ uip->ufmi_desc = ddi_strdup(desc, KM_SLEEP);
+}
+
+void
+ddi_ufm_image_set_nslots(ddi_ufm_image_t *uip, uint_t nslots)
+{
+ VERIFY(uip != NULL);
+ uip->ufmi_nslots = nslots;
+}
+
+void
+ddi_ufm_image_set_misc(ddi_ufm_image_t *uip, nvlist_t *misc)
+{
+ VERIFY(uip != NULL && misc != NULL);
+ nvlist_free(uip->ufmi_misc);
+ uip->ufmi_misc = misc;
+}
+
+void
+ddi_ufm_slot_set_version(ddi_ufm_slot_t *usp, const char *version)
+{
+ VERIFY(usp != NULL && version != NULL);
+ if (usp->ufms_version != NULL)
+ strfree(usp->ufms_version);
+
+ usp->ufms_version = ddi_strdup(version, KM_SLEEP);
+}
+
+void
+ddi_ufm_slot_set_attrs(ddi_ufm_slot_t *usp, ddi_ufm_attr_t attr)
+{
+ VERIFY(usp != NULL && attr <= DDI_UFM_ATTR_MAX);
+ usp->ufms_attrs = attr;
+}
+
+void
+ddi_ufm_slot_set_misc(ddi_ufm_slot_t *usp, nvlist_t *misc)
+{
+ VERIFY(usp != NULL && misc != NULL);
+ nvlist_free(usp->ufms_misc);
+ usp->ufms_misc = misc;
+}
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index 41a476bef0..f1037bc936 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -21,7 +21,7 @@
#
# Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
-# Copyright 2019, Joyent, Inc.
+# Copyright 2019 Joyent, Inc.
# Copyright 2013 Garrett D'Amore <garrett@damore.org>
# Copyright 2013 Saso Kiselkov. All rights reserved.
# Copyright 2015 Igor Kozhukhov <ikozhukhov@gmail.com>
@@ -164,6 +164,8 @@ CHKHDRS= \
ddi_implfuncs.h \
ddi_obsolete.h \
ddi_periodic.h \
+ ddi_ufm.h \
+ ddi_ufm_impl.h \
ddidevmap.h \
ddidmareq.h \
ddimapreq.h \
diff --git a/usr/src/uts/common/sys/ddi_ufm.h b/usr/src/uts/common/sys/ddi_ufm.h
new file mode 100644
index 0000000000..e6ad50d9ef
--- /dev/null
+++ b/usr/src/uts/common/sys/ddi_ufm.h
@@ -0,0 +1,224 @@
+/*
+ * 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 2019 Joyent, Inc.
+ */
+
+#ifndef _SYS_DDI_UFM_H
+#define _SYS_DDI_UFM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _KERNEL
+#include <sys/cred.h>
+#include <sys/dditypes.h>
+#include <sys/nvpair.h>
+#include <sys/param.h>
+#else
+#include <sys/nvpair.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#endif /* _KERNEL */
+
+#define DDI_UFM_DEV "/dev/ufm"
+#define DDI_UFM_CURRENT_VERSION 1
+#define DDI_UFM_VERSION_ONE 1
+
+#define UFM_IOC ('u' << 24) | ('f' << 16) | ('m' << 8)
+#define UFM_IOC_GETCAPS (UFM_IOC | 1)
+#define UFM_IOC_REPORTSZ (UFM_IOC | 2)
+#define UFM_IOC_REPORT (UFM_IOC | 3)
+#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.
+ */
+typedef enum {
+ DDI_UFM_CAP_REPORT = 1 << 0,
+} ddi_ufm_cap_t;
+
+/*
+ * This struct defines the input/output data for the 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.
+ */
+typedef struct ufm_ioc_getcaps {
+ uint_t ufmg_version; /* DDI_UFM_VERSION */
+ uint_t ufmg_caps; /* UFM Caps */
+ char ufmg_devpath[MAXPATHLEN];
+} ufm_ioc_getcaps_t;
+
+/*
+ * This struct defines the input/output data for the 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 amount of space (in bytes)
+ * required to hold the UFM data for this device instance. This should be used
+ * to allocate a sufficiently size buffer for the UFM_IOC_REPORT ioctl.
+ */
+typedef struct ufm_ioc_bufsz {
+ uint_t ufbz_version; /* DDI_UFM_VERSION */
+ size_t ufbz_size; /* sz of buf to be returned by ioctl */
+ char ufbz_devpath[MAXPATHLEN];
+} ufm_ioc_bufsz_t;
+
+#ifdef _KERNEL
+typedef struct ufm_ioc_bufsz32 {
+ uint_t ufbz_version;
+ size32_t ufbz_size;
+ char ufbz_devpath[MAXPATHLEN];
+} ufm_ioc_bufsz32_t;
+#endif /* _KERNEL */
+
+/*
+ * This struct defines the input/output data for the UFM_IOC_REPORT ioctl.
+ * Callers should specify the ufmr_version, ufmr_bufsz and ufmr_devpath fields.
+ * On success, the ufmr_buf field will point to a packed nvlist containing the
+ * UFM data for the specified device instance. The value of ufmr_bufsz will be
+ * updated to reflect the actual size of data copied out.
+ */
+typedef struct ufm_ioc_report {
+ uint_t ufmr_version; /* DDI_UFM_VERSION */
+ 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;
+
+#ifdef _KERNEL
+typedef struct ufm_ioc_report32 {
+ uint_t ufmr_version;
+ size32_t ufmr_bufsz;
+ caddr32_t ufmr_buf;
+ char ufmr_devpath[MAXPATHLEN];
+} ufm_ioc_report32_t;
+#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
+ * description of the image and an optional nvlist nvpair containing
+ * miscellaneous image information.
+ */
+#define DDI_UFM_NV_IMAGES "ufm-images"
+#define DDI_UFM_NV_IMAGE_DESC "ufm-image-description"
+#define DDI_UFM_NV_IMAGE_MISC "ufm-image-misc"
+
+/*
+ * Each image nvlist also contains an array of nvlists representing the slots.
+ */
+#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.
+ */
+#define DDI_UFM_NV_SLOT_VERSION "ufm-slot-version"
+
+typedef enum {
+ DDI_UFM_ATTR_READABLE = 1 << 0,
+ DDI_UFM_ATTR_WRITEABLE = 1 << 1,
+ DDI_UFM_ATTR_ACTIVE = 1 << 2,
+ DDI_UFM_ATTR_EMPTY = 1 << 3
+} ddi_ufm_attr_t;
+
+#define DDI_UFM_ATTR_MAX DDI_UFM_ATTR_READABLE | \
+ DDI_UFM_ATTR_WRITEABLE | \
+ DDI_UFM_ATTR_ACTIVE | \
+ DDI_UFM_ATTR_EMPTY
+
+#define DDI_UFM_NV_SLOT_ATTR "ufm-slot-attributes"
+
+#define DDI_UFM_NV_SLOT_MISC "ufm-slot-misc"
+
+#ifdef _KERNEL
+/* opaque structures */
+typedef struct ddi_ufm_handle ddi_ufm_handle_t;
+typedef struct ddi_ufm_image ddi_ufm_image_t;
+typedef struct ddi_ufm_slot ddi_ufm_slot_t;
+
+/*
+ * DDI UFM Operations vector
+ */
+typedef struct ddi_ufm_ops {
+ int (*ddi_ufm_op_nimages)(ddi_ufm_handle_t *, void *, uint_t *);
+ int (*ddi_ufm_op_fill_image)(ddi_ufm_handle_t *, void *, uint_t,
+ ddi_ufm_image_t *);
+ 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 *);
+} ddi_ufm_ops_t;
+
+/*
+ * During a device driver's attach(9E) entry point, a device driver should
+ * register with the UFM subsystem by filling out a UFM operations vector
+ * (see above) and then calling ddi_ufm_init(9F). The driver may pass in a
+ * value, usually a pointer to its soft state pointer, which it will then
+ * receive when its subsequent entry points are called.
+ */
+int ddi_ufm_init(dev_info_t *, uint_t version, ddi_ufm_ops_t *,
+ ddi_ufm_handle_t **, void *);
+
+/*
+ * Device drivers should call ddi_ufm_update(9F) after driver initialization is
+ * complete and after calling ddi_ufm_init(9F), in order to indicate to the
+ * UFM subsystem that the driver is in a state where it is ready to receive
+ * calls to its UFM entry points.
+ *
+ * Additionally, whenever the driver detects a change in the state of a UFM, it
+ * should call ddi_ufm_update(9F). This will cause the UFM subsystem to
+ * invalidate any cached state regarding this driver's UFM(s)
+ */
+void ddi_ufm_update(ddi_ufm_handle_t *);
+
+/*
+ * A device driver should call ddi_ufm_fini(9F) during its detach(9E) entry
+ * point. Upon return, the driver is gaurunteed that no further DDI UFM entry
+ * points will be called and thus any related state can be safely torn down.
+ *
+ * After return, the UFM handle is no longer valid and should not be used in
+ * any future ddi_ufm_* calls.
+ */
+void ddi_ufm_fini(ddi_ufm_handle_t *);
+
+/*
+ * These interfaces should only be called within the context of a
+ * ddi_ufm_op_fill_image callback.
+ */
+void ddi_ufm_image_set_desc(ddi_ufm_image_t *, const char *);
+void ddi_ufm_image_set_nslots(ddi_ufm_image_t *, uint_t);
+void ddi_ufm_image_set_misc(ddi_ufm_image_t *, nvlist_t *);
+
+/*
+ * These interfaces should only be called within the context of a
+ * ddi_ufm_op_fill_slot callback.
+ */
+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 *);
+#endif /* _KERNEL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DDI_UFM_H */
diff --git a/usr/src/uts/common/sys/ddi_ufm_impl.h b/usr/src/uts/common/sys/ddi_ufm_impl.h
new file mode 100644
index 0000000000..49fd3b9246
--- /dev/null
+++ b/usr/src/uts/common/sys/ddi_ufm_impl.h
@@ -0,0 +1,87 @@
+/*
+ * 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 2019 Joyent, Inc.
+ */
+
+#ifndef _SYS_DDI_UFM_IMPL_H
+#define _SYS_DDI_UFM_IMPL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/avl.h>
+#include <sys/ddi_ufm.h>
+#include <sys/mutex.h>
+#include <sys/nvpair.h>
+#include <sys/types.h>
+
+typedef enum {
+ DDI_UFM_STATE_INIT = 1 << 0,
+ DDI_UFM_STATE_READY = 1 << 1,
+ DDI_UFM_STATE_SHUTTING_DOWN = 1 << 2
+} ddi_ufm_state_t;
+
+/* private interface for startup_ddi() */
+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);
+
+struct ddi_ufm_slot {
+ uint_t ufms_slotno;
+ char *ufms_version;
+ ddi_ufm_attr_t ufms_attrs;
+ nvlist_t *ufms_misc;
+};
+
+struct ddi_ufm_image {
+ uint_t ufmi_imageno;
+ char *ufmi_desc;
+ nvlist_t *ufmi_misc;
+ struct ddi_ufm_slot *ufmi_slots;
+ uint_t ufmi_nslots;
+};
+
+struct ddi_ufm_handle {
+ /*
+ * The following fields get filled in when a UFM-aware driver calls
+ * ddi_ufm_init(9E). They remain valid until the driver calls
+ * ddi_ufm_fini(9E). You can test for validity of these fields by
+ * checking if the DDI_UFM_STATE_INIT flag is set in ufmh_state.
+ */
+ kmutex_t ufmh_lock;
+ char ufmh_devpath[MAXPATHLEN];
+ ddi_ufm_ops_t *ufmh_ops;
+ void *ufmh_arg;
+ uint_t ufmh_state;
+ uint_t ufmh_version;
+ /*
+ * The following four fields represent lazily cached UFM data
+ * retrieved from a UFM-aware driver. If ufmh_report is non-NULL
+ * then all four of these fields will contain valid data.
+ */
+ struct ddi_ufm_image *ufmh_images;
+ uint_t ufmh_nimages;
+ ddi_ufm_cap_t ufmh_caps;
+ nvlist_t *ufmh_report;
+
+ avl_node_t ufmh_link;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DDI_UFM_IMPL_H */
diff --git a/usr/src/uts/common/sys/scsi/adapters/mpt_sas/mptsas_var.h b/usr/src/uts/common/sys/scsi/adapters/mpt_sas/mptsas_var.h
index 0050c8c00f..ba340549c6 100644
--- a/usr/src/uts/common/sys/scsi/adapters/mpt_sas/mptsas_var.h
+++ b/usr/src/uts/common/sys/scsi/adapters/mpt_sas/mptsas_var.h
@@ -22,7 +22,7 @@
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
- * Copyright (c) 2017, Joyent, Inc.
+ * Copyright 2019 Joyent, Inc.
* Copyright (c) 2014, Tegile Systems Inc. All rights reserved.
*/
@@ -61,6 +61,7 @@
#include <sys/isa_defs.h>
#include <sys/sunmdi.h>
#include <sys/mdi_impldefs.h>
+#include <sys/ddi_ufm.h>
#include <sys/scsi/adapters/mpt_sas/mptsas_hash.h>
#include <sys/scsi/adapters/mpt_sas/mptsas_ioctl.h>
#include <sys/scsi/adapters/mpt_sas/mpi/mpi2_tool.h>
@@ -914,6 +915,9 @@ typedef struct mptsas {
uint16_t m_dev_handle;
uint16_t m_smp_devhdl;
+ /* DDI UFM Handle */
+ ddi_ufm_handle_t *m_ufmh;
+
/*
* Event recording
*/
diff --git a/usr/src/uts/intel/Makefile.intel b/usr/src/uts/intel/Makefile.intel
index bb47d45f03..dabde6b98f 100644
--- a/usr/src/uts/intel/Makefile.intel
+++ b/usr/src/uts/intel/Makefile.intel
@@ -347,6 +347,8 @@ DRV_KMODS += trill
DRV_KMODS += udp
DRV_KMODS += udp6
DRV_KMODS += ucode
+DRV_KMODS += ufm
+DRV_KMODS += ufmtest
DRV_KMODS += ural
DRV_KMODS += uath
DRV_KMODS += urtw
diff --git a/usr/src/uts/intel/ufm/Makefile b/usr/src/uts/intel/ufm/Makefile
new file mode 100644
index 0000000000..ee16a1d019
--- /dev/null
+++ b/usr/src/uts/intel/ufm/Makefile
@@ -0,0 +1,48 @@
+#
+# 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 2019 Joyent, Inc.
+#
+
+UTSBASE = ../..
+
+MODULE = ufm
+OBJECTS = $(OBJS_DIR)/ufm.o
+LINTS = $(LINTS_DIR)/ufm.ln
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ
diff --git a/usr/src/uts/intel/ufmtest/Makefile b/usr/src/uts/intel/ufmtest/Makefile
new file mode 100644
index 0000000000..3b4ea07f85
--- /dev/null
+++ b/usr/src/uts/intel/ufmtest/Makefile
@@ -0,0 +1,48 @@
+#
+# 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 2019 Joyent, Inc.
+#
+
+UTSBASE = ../..
+
+MODULE = ufmtest
+OBJECTS = $(OBJS_DIR)/ufmtest.o
+LINTS = $(LINTS_DIR)/ufmtest.ln
+ROOTMODULE = $(USR_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ
diff --git a/usr/src/uts/sparc/Makefile.sparc b/usr/src/uts/sparc/Makefile.sparc
index 0777cc5c30..912362ffbc 100644
--- a/usr/src/uts/sparc/Makefile.sparc
+++ b/usr/src/uts/sparc/Makefile.sparc
@@ -22,7 +22,7 @@
#
# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2013 Andrew Stormont. All rights reserved.
-# Copyright (c) 2015, Joyent, Inc. All rights reserved.
+# Copyright 2019 Joyent, Inc.
# Copyright 2016 Gary Mills
# Copyright 2016 Nexenta Systems, Inc.
# Copyright 2019 RackTop Systems
@@ -243,6 +243,8 @@ DRV_KMODS += bpf
DRV_KMODS += dca
DRV_KMODS += eventfd
DRV_KMODS += signalfd
+DRV_KMODS += ufm
+DRV_KMODS += ufmtest
#
# Hardware Drivers in common space
diff --git a/usr/src/uts/sparc/ufm/Makefile b/usr/src/uts/sparc/ufm/Makefile
new file mode 100644
index 0000000000..abe94f08fc
--- /dev/null
+++ b/usr/src/uts/sparc/ufm/Makefile
@@ -0,0 +1,48 @@
+#
+# 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 2019 Joyent, Inc.
+#
+
+UTSBASE = ../..
+
+MODULE = ufm
+OBJECTS = $(OBJS_DIR)/ufm.o
+LINTS = $(LINTS_DIR)/ufm.ln
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io
+
+include $(UTSBASE)/sparc/Makefile.sparc
+
+ALL_TARGET = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+include $(UTSBASE)/sparc/Makefile.targ
diff --git a/usr/src/uts/sparc/ufmtest/Makefile b/usr/src/uts/sparc/ufmtest/Makefile
new file mode 100644
index 0000000000..44827a5d96
--- /dev/null
+++ b/usr/src/uts/sparc/ufmtest/Makefile
@@ -0,0 +1,48 @@
+#
+# 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 2019 Joyent, Inc.
+#
+
+UTSBASE = ../..
+
+MODULE = ufmtest
+OBJECTS = $(OBJS_DIR)/ufmtest.o
+LINTS = $(LINTS_DIR)/ufmtest.ln
+ROOTMODULE = $(USR_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io
+
+include $(UTSBASE)/sparc/Makefile.sparc
+
+ALL_TARGET = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+include $(UTSBASE)/sparc/Makefile.targ