diff --git a/sys/kern/device_if.m b/sys/kern/device_if.m index 8b97f4e0d64f..d7609b95825f 100644 --- a/sys/kern/device_if.m +++ b/sys/kern/device_if.m @@ -57,6 +57,11 @@ CODE { { return 0; } + + static int null_quiesce(device_t dev) + { + return EOPNOTSUPP; + } }; /** @@ -283,3 +288,29 @@ METHOD int suspend { METHOD int resume { device_t dev; } DEFAULT null_resume; + +/** + * @brief This is called when the driver is asked to quiesce itself. + * + * The driver should arrange for the orderly shutdown of this device. + * All further access to the device should be curtailed. Soon there + * will be a request to detach, but there won't necessarily be one. + * + * To include this method in a device driver, use a line like this + * in the driver's method list: + * + * @code + * KOBJMETHOD(device_quiesce, foo_quiesce) + * @endcode + * + * @param dev the device being quiesced + * + * @retval 0 success + * @retval non-zero an error occurred while attempting to quiesce the + * device + * + * @see DEVICE_DETACH() + */ +METHOD int quiesce { + device_t dev; +} DEFAULT null_quiesce; diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c index ca9b03b9fb1d..be02279c0082 100644 --- a/sys/kern/subr_bus.c +++ b/sys/kern/subr_bus.c @@ -947,6 +947,71 @@ devclass_delete_driver(devclass_t busclass, driver_t *driver) return (0); } +/** + * @brief Quiesces a set of device drivers from a device class + * + * Quiesce a device driver from a devclass. This is normally called + * automatically by DRIVER_MODULE(). + * + * If the driver is currently attached to any devices, + * devclass_quiesece_driver() will first attempt to quiesce each + * device. + * + * @param dc the devclass to edit + * @param driver the driver to unregister + */ +int +devclass_quiesce_driver(devclass_t busclass, driver_t *driver) +{ + devclass_t dc = devclass_find(driver->name); + driverlink_t dl; + device_t dev; + int i; + int error; + + PDEBUG(("%s from devclass %s", driver->name, DEVCLANAME(busclass))); + + if (!dc) + return (0); + + /* + * Find the link structure in the bus' list of drivers. + */ + TAILQ_FOREACH(dl, &busclass->drivers, link) { + if (dl->driver == driver) + break; + } + + if (!dl) { + PDEBUG(("%s not found in %s list", driver->name, + busclass->name)); + return (ENOENT); + } + + /* + * Quiesce all devices. We iterate through all the devices in + * the devclass of the driver and quiesce any which are using + * the driver and which have a parent in the devclass which we + * are quiescing. + * + * Note that since a driver can be in multiple devclasses, we + * should not quiesce devices which are not children of + * devices in the affected devclass. + */ + for (i = 0; i < dc->maxunit; i++) { + if (dc->devices[i]) { + dev = dc->devices[i]; + if (dev->driver == driver && dev->parent && + dev->parent->devclass == busclass) { + if ((error = device_quiesce(dev)) != 0) + return (error); + } + } + } + + return (0); +} + /** * @internal */ @@ -2313,6 +2378,32 @@ device_detach(device_t dev) return (0); } +/** + * @brief Tells a driver to quiesce itself. + * + * This function is a wrapper around the DEVICE_QUIESCE() driver + * method. If the call to DEVICE_QUIESCE() succeeds. + * + * @param dev the device to quiesce + * + * @retval 0 success + * @retval ENXIO no driver was found + * @retval ENOMEM memory allocation failure + * @retval non-zero some other unix error code + */ +int +device_quiesce(device_t dev) +{ + + PDEBUG(("%s", DEVICENAME(dev))); + if (dev->state == DS_BUSY) + return (EBUSY); + if (dev->state != DS_ATTACHED) + return (0); + + return (DEVICE_QUIESCE(dev)); +} + /** * @brief Notify a device of system shutdown * @@ -3561,6 +3652,16 @@ driver_module_handler(module_t mod, int what, void *arg) error = devclass_delete_driver(bus_devclass, dmd->dmd_driver); + if (!error && dmd->dmd_chainevh) + error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); + break; + case MOD_QUIESCE: + PDEBUG(("Quiesce module: driver %s from bus %s", + DRIVERNAME(dmd->dmd_driver), + dmd->dmd_busname)); + error = devclass_quiesce_driver(bus_devclass, + dmd->dmd_driver); + if (!error && dmd->dmd_chainevh) error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); break; diff --git a/sys/sys/bus.h b/sys/sys/bus.h index 14d6a07a3c47..f360b39d45a7 100644 --- a/sys/sys/bus.h +++ b/sys/sys/bus.h @@ -357,6 +357,7 @@ int device_is_quiet(device_t dev); int device_print_prettyname(device_t dev); int device_printf(device_t dev, const char *, ...) __printflike(2, 3); int device_probe_and_attach(device_t dev); +int device_quiesce(device_t dev); void device_quiet(device_t dev); void device_set_desc(device_t dev, const char* desc); void device_set_desc_copy(device_t dev, const char* desc); @@ -388,6 +389,7 @@ void devclass_set_parent(devclass_t dc, devclass_t pdc); devclass_t devclass_get_parent(devclass_t dc); struct sysctl_ctx_list *devclass_get_sysctl_ctx(devclass_t dc); struct sysctl_oid *devclass_get_sysctl_tree(devclass_t dc); +int devclass_quiesce_driver(devclass_t dc, kobj_class_t driver); /* * Access functions for device resources.