summaryrefslogtreecommitdiffstats
path: root/sys/kern/subr_bus.c
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2009-06-09 14:26:23 +0000
committerjhb <jhb@FreeBSD.org>2009-06-09 14:26:23 +0000
commit77373ed4681d2d7d7a25a619e896c065e12976c6 (patch)
tree10671dffb6ce198477de5fc2d00a79d3d795b1a4 /sys/kern/subr_bus.c
parent7e92c698d93e930bd9f98da57fa435a205f23986 (diff)
downloadFreeBSD-src-77373ed4681d2d7d7a25a619e896c065e12976c6.zip
FreeBSD-src-77373ed4681d2d7d7a25a619e896c065e12976c6.tar.gz
Add support for multiple passes of the device tree during the boot-time
probe. The current device order is unchanged. This commit just adds the infrastructure and ABI changes so that it is easier to merge later changes into 8.x. - Driver attachments now have an associated pass level. Attachments are not allowed to probe or attach to drivers until the system-wide pass level is >= the attachment's pass level. By default driver attachments use the "last" pass level (BUS_PASS_DEFAULT). Driver's that wish to probe during an earlier pass use EARLY_DRIVER_MODULE() instead of DRIVER_MODULE() which accepts the pass level as an additional parameter. - A new method BUS_NEW_PASS has been added to the bus interface. This method is invoked when the system-wide pass level is changed to kick off a rescan of the device tree so that drivers that have just been made "eligible" can probe and attach. - The bus_generic_new_pass() function provides a default implementation of BUS_NEW_PASS(). It first allows drivers that were just made eligible for this pass to identify new child devices. Then it propogates the rescan to child devices that already have an attached driver by invoking their BUS_NEW_PASS() method. It also reprobes devices without a driver. - BUS_PROBE_NOMATCH() is only invoked for devices that do not have an attached driver after being scanned during the final pass. - The bus_set_pass() function is used during boot to raise the pass level. Currently it is only called once during root_bus_configure() to raise the pass level to BUS_PASS_DEFAULT. This has the effect of probing all devices in a single pass identical to previous behavior. Reviewed by: imp Approved by: re (kib)
Diffstat (limited to 'sys/kern/subr_bus.c')
-rw-r--r--sys/kern/subr_bus.c164
1 files changed, 152 insertions, 12 deletions
diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c
index 39ec6d4..c856063 100644
--- a/sys/kern/subr_bus.c
+++ b/sys/kern/subr_bus.c
@@ -66,6 +66,8 @@ typedef struct driverlink *driverlink_t;
struct driverlink {
kobj_class_t driver;
TAILQ_ENTRY(driverlink) link; /* list of drivers in devclass */
+ int pass;
+ TAILQ_ENTRY(driverlink) passlink;
};
/*
@@ -759,12 +761,98 @@ static kobj_method_t null_methods[] = {
DEFINE_CLASS(null, null_methods, 0);
/*
+ * Bus pass implementation
+ */
+
+static driver_list_t passes = TAILQ_HEAD_INITIALIZER(passes);
+int bus_current_pass = BUS_PASS_ROOT;
+
+/**
+ * @internal
+ * @brief Register the pass level of a new driver attachment
+ *
+ * Register a new driver attachment's pass level. If no driver
+ * attachment with the same pass level has been added, then @p new
+ * will be added to the global passes list.
+ *
+ * @param new the new driver attachment
+ */
+static void
+driver_register_pass(struct driverlink *new)
+{
+ struct driverlink *dl;
+
+ /* We only consider pass numbers during boot. */
+ if (bus_current_pass == BUS_PASS_DEFAULT)
+ return;
+
+ /*
+ * Walk the passes list. If we already know about this pass
+ * then there is nothing to do. If we don't, then insert this
+ * driver link into the list.
+ */
+ TAILQ_FOREACH(dl, &passes, passlink) {
+ if (dl->pass < new->pass)
+ continue;
+ if (dl->pass == new->pass)
+ return;
+ TAILQ_INSERT_BEFORE(dl, new, passlink);
+ return;
+ }
+ TAILQ_INSERT_TAIL(&passes, new, passlink);
+}
+
+/**
+ * @brief Raise the current bus pass
+ *
+ * Raise the current bus pass level to @p pass. Call the BUS_NEW_PASS()
+ * method on the root bus to kick off a new device tree scan for each
+ * new pass level that has at least one driver.
+ */
+void
+bus_set_pass(int pass)
+{
+ struct driverlink *dl;
+
+ if (bus_current_pass > pass)
+ panic("Attempt to lower bus pass level");
+
+ TAILQ_FOREACH(dl, &passes, passlink) {
+ /* Skip pass values below the current pass level. */
+ if (dl->pass <= bus_current_pass)
+ continue;
+
+ /*
+ * Bail once we hit a driver with a pass level that is
+ * too high.
+ */
+ if (dl->pass > pass)
+ break;
+
+ /*
+ * Raise the pass level to the next level and rescan
+ * the tree.
+ */
+ bus_current_pass = dl->pass;
+ BUS_NEW_PASS(root_bus);
+ }
+
+ /*
+ * If there isn't a driver registered for the requested pass,
+ * then bus_current_pass might still be less than 'pass'. Set
+ * it to 'pass' in that case.
+ */
+ if (bus_current_pass < pass)
+ bus_current_pass = pass;
+ KASSERT(bus_current_pass == pass, ("Failed to update bus pass level"));
+}
+
+/*
* Devclass implementation
*/
static devclass_list_t devclasses = TAILQ_HEAD_INITIALIZER(devclasses);
-
/**
* @internal
* @brief Find or create a device class
@@ -912,12 +1000,16 @@ devclass_driver_added(devclass_t dc, driver_t *driver)
* @param driver the driver to register
*/
int
-devclass_add_driver(devclass_t dc, driver_t *driver)
+devclass_add_driver(devclass_t dc, driver_t *driver, int pass)
{
driverlink_t dl;
PDEBUG(("%s", DRIVERNAME(driver)));
+ /* Don't allow invalid pass values. */
+ if (pass <= BUS_PASS_ROOT)
+ return (EINVAL);
+
dl = malloc(sizeof *dl, M_BUS, M_NOWAIT|M_ZERO);
if (!dl)
return (ENOMEM);
@@ -938,6 +1030,8 @@ devclass_add_driver(devclass_t dc, driver_t *driver)
dl->driver = driver;
TAILQ_INSERT_TAIL(&dc->drivers, dl, link);
driver->refs++; /* XXX: kobj_mtx */
+ dl->pass = pass;
+ driver_register_pass(dl);
devclass_driver_added(dc, driver);
bus_data_generation_update();
@@ -1801,6 +1895,11 @@ device_probe_child(device_t dev, device_t child)
for (dl = first_matching_driver(dc, child);
dl;
dl = next_matching_driver(dc, child, dl)) {
+
+ /* If this driver's pass is too high, then ignore it. */
+ if (dl->pass > bus_current_pass)
+ continue;
+
PDEBUG(("Trying %s", DRIVERNAME(dl->driver)));
device_set_driver(child, dl->driver);
if (!hasclass) {
@@ -2442,8 +2541,9 @@ device_probe(device_t dev)
}
return (-1);
}
- if ((error = device_probe_child(dev->parent, dev)) != 0) {
- if (!(dev->flags & DF_DONENOMATCH)) {
+ if ((error = device_probe_child(dev->parent, dev)) != 0) {
+ if (bus_current_pass == BUS_PASS_DEFAULT &&
+ !(dev->flags & DF_DONENOMATCH)) {
BUS_PROBE_NOMATCH(dev->parent, dev);
devnomatch(dev);
dev->flags |= DF_DONENOMATCH;
@@ -2988,6 +3088,17 @@ bus_generic_probe(device_t dev)
driverlink_t dl;
TAILQ_FOREACH(dl, &dc->drivers, link) {
+ /*
+ * If this driver's pass is too high, then ignore it.
+ * For most drivers in the default pass, this will
+ * never be true. For early-pass drivers they will
+ * only call the identify routines of eligible drivers
+ * when this routine is called. Drivers for later
+ * passes should have their identify routines called
+ * on early-pass busses during BUS_NEW_PASS().
+ */
+ if (dl->pass > bus_current_pass)
+ continue;
DEVICE_IDENTIFY(dl->driver, dev);
}
@@ -3215,6 +3326,36 @@ bus_generic_driver_added(device_t dev, driver_t *driver)
}
/**
+ * @brief Helper function for implementing BUS_NEW_PASS().
+ *
+ * This implementing of BUS_NEW_PASS() first calls the identify
+ * routines for any drivers that probe at the current pass. Then it
+ * walks the list of devices for this bus. If a device is already
+ * attached, then it calls BUS_NEW_PASS() on that device. If the
+ * device is not already attached, it attempts to attach a driver to
+ * it.
+ */
+void
+bus_generic_new_pass(device_t dev)
+{
+ driverlink_t dl;
+ devclass_t dc;
+ device_t child;
+
+ dc = dev->devclass;
+ TAILQ_FOREACH(dl, &dc->drivers, link) {
+ if (dl->pass == bus_current_pass)
+ DEVICE_IDENTIFY(dl->driver, dev);
+ }
+ TAILQ_FOREACH(child, &dev->children, link) {
+ if (child->state >= DS_ATTACHED)
+ BUS_NEW_PASS(child);
+ else if (child->state == DS_NOTPRESENT)
+ device_probe_and_attach(child);
+ }
+}
+
+/**
* @brief Helper function for implementing BUS_SETUP_INTR().
*
* This simple implementation of BUS_SETUP_INTR() simply calls the
@@ -3912,13 +4053,11 @@ DECLARE_MODULE(rootbus, root_bus_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
void
root_bus_configure(void)
{
- device_t dev;
PDEBUG(("."));
- TAILQ_FOREACH(dev, &root_bus->children, link) {
- device_probe_and_attach(dev);
- }
+ /* Eventually this will be split up, but this is sufficient for now. */
+ bus_set_pass(BUS_PASS_DEFAULT);
}
/**
@@ -3932,10 +4071,10 @@ root_bus_configure(void)
int
driver_module_handler(module_t mod, int what, void *arg)
{
- int error;
struct driver_module_data *dmd;
devclass_t bus_devclass;
kobj_class_t driver;
+ int error, pass;
dmd = (struct driver_module_data *)arg;
bus_devclass = devclass_find_internal(dmd->dmd_busname, NULL, TRUE);
@@ -3946,10 +4085,11 @@ driver_module_handler(module_t mod, int what, void *arg)
if (dmd->dmd_chainevh)
error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg);
+ pass = dmd->dmd_pass;
driver = dmd->dmd_driver;
- PDEBUG(("Loading module: driver %s on bus %s",
- DRIVERNAME(driver), dmd->dmd_busname));
- error = devclass_add_driver(bus_devclass, driver);
+ PDEBUG(("Loading module: driver %s on bus %s (pass %d)",
+ DRIVERNAME(driver), dmd->dmd_busname, pass));
+ error = devclass_add_driver(bus_devclass, driver, pass);
if (error)
break;
OpenPOWER on IntegriCloud