aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base/dd.c
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2005-05-06 15:38:33 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2005-06-20 18:15:28 -0400
commitc95a6b057b108c2b7add35cba1354f9af921349e (patch)
tree5a312f634b0aec295201a93020ba025d840e5f21 /drivers/base/dd.c
parent6623415687eaffef49429292ab062bb046ee3311 (diff)
[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: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/base/dd.c')
-rw-r--r--drivers/base/dd.c51
1 files changed, 38 insertions, 13 deletions
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index 8510918109e0..eab2030c506d 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -177,41 +177,66 @@ void driver_attach(struct device_driver * drv)
177 * @dev: device. 177 * @dev: device.
178 * 178 *
179 * Manually detach device from driver. 179 * Manually detach device from driver.
180 * Note that this is called without incrementing the bus 180 *
181 * reference count nor taking the bus's rwsem. Be sure that 181 * __device_release_driver() must be called with @dev->sem held.
182 * those are accounted for before calling this function.
183 */ 182 */
184void device_release_driver(struct device * dev) 183
184static void __device_release_driver(struct device * dev)
185{ 185{
186 struct device_driver * drv; 186 struct device_driver * drv;
187 187
188 down(&dev->sem); 188 drv = dev->driver;
189 if (dev->driver) { 189 if (drv) {
190 drv = dev->driver; 190 get_driver(drv);
191 sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj)); 191 sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj));
192 sysfs_remove_link(&dev->kobj, "driver"); 192 sysfs_remove_link(&dev->kobj, "driver");
193 klist_del(&dev->knode_driver); 193 klist_remove(&dev->knode_driver);
194 194
195 if (drv->remove) 195 if (drv->remove)
196 drv->remove(dev); 196 drv->remove(dev);
197 dev->driver = NULL; 197 dev->driver = NULL;
198 put_driver(drv);
198 } 199 }
199 up(&dev->sem);
200} 200}
201 201
202static int __remove_driver(struct device * dev, void * unused) 202void device_release_driver(struct device * dev)
203{ 203{
204 device_release_driver(dev); 204 /*
205 return 0; 205 * If anyone calls device_release_driver() recursively from
206 * within their ->remove callback for the same device, they
207 * will deadlock right here.
208 */
209 down(&dev->sem);
210 __device_release_driver(dev);
211 up(&dev->sem);
206} 212}
207 213
214
208/** 215/**
209 * driver_detach - detach driver from all devices it controls. 216 * driver_detach - detach driver from all devices it controls.
210 * @drv: driver. 217 * @drv: driver.
211 */ 218 */
212void driver_detach(struct device_driver * drv) 219void driver_detach(struct device_driver * drv)
213{ 220{
214 driver_for_each_device(drv, NULL, NULL, __remove_driver); 221 struct device * dev;
222
223 for (;;) {
224 spin_lock_irq(&drv->klist_devices.k_lock);
225 if (list_empty(&drv->klist_devices.k_list)) {
226 spin_unlock_irq(&drv->klist_devices.k_lock);
227 break;
228 }
229 dev = list_entry(drv->klist_devices.k_list.prev,
230 struct device, knode_driver.n_node);
231 get_device(dev);
232 spin_unlock_irq(&drv->klist_devices.k_lock);
233
234 down(&dev->sem);
235 if (dev->driver == drv)
236 __device_release_driver(dev);
237 up(&dev->sem);
238 put_device(dev);
239 }
215} 240}
216 241
217 242