diff options
author | jhb <jhb@FreeBSD.org> | 2011-03-01 14:43:37 +0000 |
---|---|---|
committer | jhb <jhb@FreeBSD.org> | 2011-03-01 14:43:37 +0000 |
commit | d64a5d112c6bb28a44c7b9377ca38c1876b8005b (patch) | |
tree | 66671d19bb9da535c3e6eca13cd6c56f362923f2 | |
parent | 4c48fabb06c83176bbacf0aa02e57d294e3074f9 (diff) | |
download | FreeBSD-src-d64a5d112c6bb28a44c7b9377ca38c1876b8005b.zip FreeBSD-src-d64a5d112c6bb28a44c7b9377ca38c1876b8005b.tar.gz |
Similar to 189574, properly handle subclasses of bus drivers when deleting
a driver during kldunload. Specifically, recursively walk the tree of
subclasses of a given driver attachment's bus device class detaching all
instances of that driver for each class and its subclasses.
Reported by: bschmidt
Reviewed by: imp
MFC after: 1 week
-rw-r--r-- | sys/kern/subr_bus.c | 111 |
1 files changed, 81 insertions, 30 deletions
diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c index 30d374a..f6b1ecc 100644 --- a/sys/kern/subr_bus.c +++ b/sys/kern/subr_bus.c @@ -987,10 +987,12 @@ devclass_find(const char *classname) * is called by devclass_add_driver to accomplish the recursive * notification of all the children classes of dc, as well as dc. * Each layer will have BUS_DRIVER_ADDED() called for all instances of - * the devclass. We do a full search here of the devclass list at - * each iteration level to save storing children-lists in the devclass - * structure. If we ever move beyond a few dozen devices doing this, - * we may need to reevaluate... + * the devclass. + * + * We do a full search here of the devclass list at each iteration + * level to save storing children-lists in the devclass structure. If + * we ever move beyond a few dozen devices doing this, we may need to + * reevaluate... * * @param dc the devclass to edit * @param driver the driver that was just added @@ -1085,6 +1087,78 @@ devclass_add_driver(devclass_t dc, driver_t *driver, int pass, devclass_t *dcp) } /** + * @brief Register that a device driver has been deleted from a devclass + * + * Register that a device driver has been removed from a devclass. + * This is called by devclass_delete_driver to accomplish the + * recursive notification of all the children classes of busclass, as + * well as busclass. Each layer will attempt to detach the driver + * from any devices that are children of the bus's devclass. The function + * will return an error if a device fails to detach. + * + * We do a full search here of the devclass list at each iteration + * level to save storing children-lists in the devclass structure. If + * we ever move beyond a few dozen devices doing this, we may need to + * reevaluate... + * + * @param busclass the devclass of the parent bus + * @param dc the devclass of the driver being deleted + * @param driver the driver being deleted + */ +static int +devclass_driver_deleted(devclass_t busclass, devclass_t dc, driver_t *driver) +{ + devclass_t parent; + device_t dev; + int error, i; + + /* + * Disassociate from any devices. We iterate through all the + * devices in the devclass of the driver and detach any which are + * using the driver and which have a parent in the devclass which + * we are deleting from. + * + * Note that since a driver can be in multiple devclasses, we + * should not detach 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_detach(dev)) != 0) + return (error); + device_set_driver(dev, NULL); + BUS_PROBE_NOMATCH(dev->parent, dev); + devnomatch(dev); + dev->flags |= DF_DONENOMATCH; + } + } + } + + /* + * Walk through the children classes. Since we only keep a + * single parent pointer around, we walk the entire list of + * devclasses looking for children. We set the + * DC_HAS_CHILDREN flag when a child devclass is created on + * the parent, so we only walk the list for those devclasses + * that have children. + */ + if (!(busclass->flags & DC_HAS_CHILDREN)) + return (0); + parent = busclass; + TAILQ_FOREACH(busclass, &devclasses, link) { + if (busclass->parent == parent) { + error = devclass_driver_deleted(busclass, dc, driver); + if (error) + return (error); + } + } + return (0); +} + +/** * @brief Delete a device driver from a device class * * Delete a device driver from a devclass. This is normally called @@ -1103,8 +1177,6 @@ devclass_delete_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))); @@ -1126,30 +1198,9 @@ devclass_delete_driver(devclass_t busclass, driver_t *driver) return (ENOENT); } - /* - * Disassociate from any devices. We iterate through all the - * devices in the devclass of the driver and detach any which are - * using the driver and which have a parent in the devclass which - * we are deleting from. - * - * Note that since a driver can be in multiple devclasses, we - * should not detach 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_detach(dev)) != 0) - return (error); - device_set_driver(dev, NULL); - BUS_PROBE_NOMATCH(dev->parent, dev); - devnomatch(dev); - dev->flags |= DF_DONENOMATCH; - } - } - } + error = devclass_driver_deleted(busclass, dc, driver); + if (error != 0) + return (error); TAILQ_REMOVE(&busclass->drivers, dl, link); free(dl, M_BUS); |