diff options
Diffstat (limited to 'drivers/base/dd.c')
-rw-r--r-- | drivers/base/dd.c | 51 |
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 | */ |
184 | void device_release_driver(struct device * dev) | 183 | |
184 | static 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 | ||
202 | static int __remove_driver(struct device * dev, void * unused) | 202 | void 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 | */ |
212 | void driver_detach(struct device_driver * drv) | 219 | void 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 | ||