diff options
author | Rob Johnston <rob.johnston@joyent.com> | 2019-02-19 19:46:39 +0000 |
---|---|---|
committer | Richard Lowe <richlowe@richlowe.net> | 2019-07-26 16:34:02 +0000 |
commit | 508a0e8cf1600b06c1f7361ad76e736710d3fdf8 (patch) | |
tree | b0210a4c46ddc28f53e733f51476a58fb9a81606 | |
parent | 391889ecff3f697040bc0677f3fb4a002d562e31 (diff) | |
download | illumos-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>
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 |