diff options
Diffstat (limited to 'usr/src/uts/common/os/devcfg.c')
-rw-r--r-- | usr/src/uts/common/os/devcfg.c | 187 |
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)); |