summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/kern/device_if.m31
-rw-r--r--sys/kern/subr_bus.c101
-rw-r--r--sys/sys/bus.h2
3 files changed, 134 insertions, 0 deletions
diff --git a/sys/kern/device_if.m b/sys/kern/device_if.m
index 8b97f4e..d7609b9 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 ca9b03b..be02279 100644
--- a/sys/kern/subr_bus.c
+++ b/sys/kern/subr_bus.c
@@ -948,6 +948,71 @@ devclass_delete_driver(devclass_t busclass, driver_t *driver)
}
/**
+ * @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
*/
static driverlink_t
@@ -2314,6 +2379,32 @@ device_detach(device_t dev)
}
/**
+ * @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
*
* This function calls the DEVICE_SHUTDOWN() driver method if the
@@ -3564,6 +3655,16 @@ driver_module_handler(module_t mod, int what, void *arg)
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;
default:
error = EOPNOTSUPP;
break;
diff --git a/sys/sys/bus.h b/sys/sys/bus.h
index 14d6a07..f360b39 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.
OpenPOWER on IntegriCloud