Commit c95a6b05 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[PATCH] driver core: Fix races in driver_detach()

This patch is intended for your "driver" tree.  It fixes several subtle
races in driver_detach() and device_release_driver() in the driver-model
core.

The major change is to use klist_remove() rather than klist_del() when
taking a device off its driver's list.  There's no other way to guarantee
that the list pointers will be updated before some other driver binds to
the device.  For this to work driver_detach() can't use a klist iterator,
so the loop over the devices must be written out in full.  In addition the
patch protects against the possibility that, when a driver and a device
are unregistered at the same time, one may be unloaded from memory before
the other is finished using it.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 66234156
...@@ -177,41 +177,66 @@ void driver_attach(struct device_driver * drv) ...@@ -177,41 +177,66 @@ void driver_attach(struct device_driver * drv)
* @dev: device. * @dev: device.
* *
* Manually detach device from driver. * Manually detach device from driver.
* Note that this is called without incrementing the bus *
* reference count nor taking the bus's rwsem. Be sure that * __device_release_driver() must be called with @dev->sem held.
* those are accounted for before calling this function.
*/ */
void device_release_driver(struct device * dev)
static void __device_release_driver(struct device * dev)
{ {
struct device_driver * drv; struct device_driver * drv;
down(&dev->sem);
if (dev->driver) {
drv = dev->driver; drv = dev->driver;
if (drv) {
get_driver(drv);
sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj)); sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj));
sysfs_remove_link(&dev->kobj, "driver"); sysfs_remove_link(&dev->kobj, "driver");
klist_del(&dev->knode_driver); klist_remove(&dev->knode_driver);
if (drv->remove) if (drv->remove)
drv->remove(dev); drv->remove(dev);
dev->driver = NULL; dev->driver = NULL;
put_driver(drv);
} }
up(&dev->sem);
} }
static int __remove_driver(struct device * dev, void * unused) void device_release_driver(struct device * dev)
{ {
device_release_driver(dev); /*
return 0; * If anyone calls device_release_driver() recursively from
* within their ->remove callback for the same device, they
* will deadlock right here.
*/
down(&dev->sem);
__device_release_driver(dev);
up(&dev->sem);
} }
/** /**
* driver_detach - detach driver from all devices it controls. * driver_detach - detach driver from all devices it controls.
* @drv: driver. * @drv: driver.
*/ */
void driver_detach(struct device_driver * drv) void driver_detach(struct device_driver * drv)
{ {
driver_for_each_device(drv, NULL, NULL, __remove_driver); struct device * dev;
for (;;) {
spin_lock_irq(&drv->klist_devices.k_lock);
if (list_empty(&drv->klist_devices.k_list)) {
spin_unlock_irq(&drv->klist_devices.k_lock);
break;
}
dev = list_entry(drv->klist_devices.k_list.prev,
struct device, knode_driver.n_node);
get_device(dev);
spin_unlock_irq(&drv->klist_devices.k_lock);
down(&dev->sem);
if (dev->driver == drv)
__device_release_driver(dev);
up(&dev->sem);
put_device(dev);
}
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment