summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/os/devcfg.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/os/devcfg.c')
-rw-r--r--usr/src/uts/common/os/devcfg.c187
1 files changed, 175 insertions, 12 deletions
diff --git a/usr/src/uts/common/os/devcfg.c b/usr/src/uts/common/os/devcfg.c
index fab3222d9b..e4571b8f0e 100644
--- a/usr/src/uts/common/os/devcfg.c
+++ b/usr/src/uts/common/os/devcfg.c
@@ -39,6 +39,7 @@
#include <sys/contract/device_impl.h>
#include <sys/dacf.h>
#include <sys/promif.h>
+#include <sys/pci.h>
#include <sys/cpuvar.h>
#include <sys/pathname.h>
#include <sys/taskq.h>
@@ -50,6 +51,7 @@
#include <sys/fs/dv_node.h>
#include <sys/reboot.h>
#include <sys/sysmacros.h>
+#include <sys/systm.h>
#include <sys/sunldi.h>
#include <sys/sunldi_impl.h>
@@ -122,7 +124,7 @@ major_t clone_major;
volatile ulong_t devtree_gen; /* generation number */
/* block all future dev_info state changes */
-static hrtime_t volatile devinfo_freeze = 0;
+hrtime_t volatile devinfo_freeze = 0;
/* number of dev_info attaches/detaches currently in progress */
static ulong_t devinfo_attach_detach = 0;
@@ -158,6 +160,8 @@ int identify_9e = 0;
int mtc_off; /* turn off mt config */
+int quiesce_debug = 0;
+
static kmem_cache_t *ddi_node_cache; /* devinfo node cache */
static devinfo_log_header_t *devinfo_audit_log; /* devinfo log */
static int devinfo_log_size; /* size in pages */
@@ -197,7 +201,7 @@ static int ndi_devi_unbind_driver(dev_info_t *dip);
static void i_ddi_check_retire(dev_info_t *dip);
-
+static void quiesce_one_device(dev_info_t *, void *);
/*
* dev_info cache and node management
@@ -3949,6 +3953,164 @@ i_ddi_prompath_to_devfspath(char *prompath, char *devfspath)
}
/*
+ * This function is intended to identify drivers that must quiesce for fast
+ * reboot to succeed. It does not claim to have more knowledge about the device
+ * than its driver. If a driver has implemented quiesce(), it will be invoked;
+ * if a so identified driver does not manage any device that needs to be
+ * quiesced, it must explicitly set its devo_quiesce dev_op to
+ * ddi_quiesce_not_needed.
+ */
+static int skip_pseudo = 1; /* Skip pseudo devices */
+static int skip_non_hw = 1; /* Skip devices with no hardware property */
+static int
+should_implement_quiesce(dev_info_t *dip)
+{
+ struct dev_info *devi = DEVI(dip);
+ dev_info_t *pdip;
+
+ /*
+ * If dip is pseudo and skip_pseudo is set, driver doesn't have to
+ * implement quiesce().
+ */
+ if (skip_pseudo &&
+ strncmp(ddi_binding_name(dip), "pseudo", sizeof ("pseudo")) == 0)
+ return (0);
+
+ /*
+ * If parent dip is pseudo and skip_pseudo is set, driver doesn't have
+ * to implement quiesce().
+ */
+ if (skip_pseudo && (pdip = ddi_get_parent(dip)) != NULL &&
+ strncmp(ddi_binding_name(pdip), "pseudo", sizeof ("pseudo")) == 0)
+ return (0);
+
+ /*
+ * If not attached, driver doesn't have to implement quiesce().
+ */
+ if (!i_ddi_devi_attached(dip))
+ return (0);
+
+ /*
+ * If dip has no hardware property and skip_non_hw is set,
+ * driver doesn't have to implement quiesce().
+ */
+ if (skip_non_hw && devi->devi_hw_prop_ptr == NULL)
+ return (0);
+
+ return (1);
+}
+
+static int
+driver_has_quiesce(struct dev_ops *ops)
+{
+ if ((ops->devo_rev >= 4) && (ops->devo_quiesce != nodev) &&
+ (ops->devo_quiesce != NULL) && (ops->devo_quiesce != nulldev) &&
+ (ops->devo_quiesce != ddi_quiesce_not_supported))
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Check to see if a driver has implemented the quiesce() DDI function.
+ */
+int
+check_driver_quiesce(dev_info_t *dip, void *arg)
+{
+ struct dev_ops *ops;
+
+ if (!should_implement_quiesce(dip))
+ return (DDI_WALK_CONTINUE);
+
+ if ((ops = ddi_get_driver(dip)) == NULL)
+ return (DDI_WALK_CONTINUE);
+
+ if (driver_has_quiesce(ops)) {
+ if ((quiesce_debug & 0x2) == 0x2) {
+ if (ops->devo_quiesce == ddi_quiesce_not_needed)
+ cmn_err(CE_CONT, "%s does not need to be "
+ "quiesced", ddi_driver_name(dip));
+ else
+ cmn_err(CE_CONT, "%s has quiesce routine",
+ ddi_driver_name(dip));
+ }
+ } else {
+ if (arg != NULL)
+ *((int *)arg) = -1;
+ cmn_err(CE_WARN, "%s has no quiesce()", ddi_driver_name(dip));
+ }
+
+ return (DDI_WALK_CONTINUE);
+}
+
+/*
+ * Quiesce device.
+ */
+static void
+quiesce_one_device(dev_info_t *dip, void *arg)
+{
+ struct dev_ops *ops;
+ int should_quiesce = 0;
+
+ /*
+ * If the device is not attached it doesn't need to be quiesced.
+ */
+ if (!i_ddi_devi_attached(dip))
+ return;
+
+ if ((ops = ddi_get_driver(dip)) == NULL)
+ return;
+
+ should_quiesce = should_implement_quiesce(dip);
+
+ /*
+ * If there's an implementation of quiesce(), always call it even if
+ * some of the drivers don't have quiesce() or quiesce() have failed
+ * so we can do force fast reboot. The implementation of quiesce()
+ * should not negatively affect a regular reboot.
+ */
+ if (driver_has_quiesce(ops)) {
+ int rc = DDI_SUCCESS;
+
+ if (ops->devo_quiesce == ddi_quiesce_not_needed)
+ return;
+
+ rc = devi_quiesce(dip);
+
+ /* quiesce() should never fail */
+ ASSERT(rc == DDI_SUCCESS);
+
+ if (rc != DDI_SUCCESS && should_quiesce) {
+
+ if (arg != NULL)
+ *((int *)arg) = -1;
+ }
+ } else if (should_quiesce && arg != NULL) {
+ *((int *)arg) = -1;
+ }
+}
+
+/*
+ * Traverse the dev info tree in a breadth-first manner so that we quiesce
+ * children first. All subtrees under the parent of dip will be quiesced.
+ */
+void
+quiesce_devices(dev_info_t *dip, void *arg)
+{
+ /*
+ * if we're reached here, the device tree better not be changing.
+ * so either devinfo_freeze better be set or we better be panicing.
+ */
+ ASSERT(devinfo_freeze || panicstr);
+
+ for (; dip != NULL; dip = ddi_get_next_sibling(dip)) {
+ quiesce_devices(ddi_get_child(dip), arg);
+
+ quiesce_one_device(dip, arg);
+ }
+}
+
+/*
* Reset all the pure leaf drivers on the system at halt time
*/
static int
@@ -4003,17 +4165,17 @@ reset_leaves(void)
(void) walk_devs(top_devinfo, reset_leaf_device, NULL, 0);
}
+
/*
- * devtree_freeze() must be called before reset_leaves() during a
- * normal system shutdown. It attempts to ensure that there are no
- * outstanding attach or detach operations in progress when reset_leaves()
- * is invoked. It must be called before the system becomes single-threaded
- * because device attach and detach are multi-threaded operations. (note
- * that during system shutdown the system doesn't actually become
- * single-thread since other threads still exist, but the shutdown thread
- * will disable preemption for itself, raise it's pil, and stop all the
- * other cpus in the system there by effectively making the system
- * single-threaded.)
+ * devtree_freeze() must be called before quiesce_devices() and reset_leaves()
+ * during a normal system shutdown. It attempts to ensure that there are no
+ * outstanding attach or detach operations in progress when quiesce_devices() or
+ * reset_leaves()is invoked. It must be called before the system becomes
+ * single-threaded because device attach and detach are multi-threaded
+ * operations. (note that during system shutdown the system doesn't actually
+ * become single-thread since other threads still exist, but the shutdown thread
+ * will disable preemption for itself, raise it's pil, and stop all the other
+ * cpus in the system there by effectively making the system single-threaded.)
*/
void
devtree_freeze(void)
@@ -7356,6 +7518,7 @@ void
i_ddi_di_cache_free(struct di_cache *cache)
{
int error;
+ extern int sys_shutdown;
ASSERT(mutex_owned(&cache->cache_lock));