#include <linux/device.h>
#include <linux/module.h>
#include <linux/kthread.h>
+#include <linux/wait.h>
#include "base.h"
#include "power/power.h"
#define to_drv(node) container_of(node, struct device_driver, kobj.entry)
-/**
- * device_bind_driver - bind a driver to one device.
- * @dev: device.
- *
- * Allow manual attachment of a driver to a device.
- * Caller must have already set @dev->driver.
- *
- * Note that this does not modify the bus reference count
- * nor take the bus's rwsem. Please verify those are accounted
- * for before calling this. (It is ok to call with no other effort
- * from a driver's probe() method.)
- *
- * This function must be called with @dev->sem held.
- */
-int device_bind_driver(struct device *dev)
+static void driver_bound(struct device *dev)
{
- int ret;
-
if (klist_node_attached(&dev->knode_driver)) {
printk(KERN_WARNING "%s: device %s already bound\n",
__FUNCTION__, kobject_name(&dev->kobj));
- return 0;
+ return;
}
pr_debug("bound device '%s' to driver '%s'\n",
dev->bus_id, dev->driver->name);
+
+ if (dev->bus)
+ blocking_notifier_call_chain(&dev->bus->bus_notifier,
+ BUS_NOTIFY_BOUND_DRIVER, dev);
+
klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices);
+}
+
+static int driver_sysfs_add(struct device *dev)
+{
+ int ret;
+
ret = sysfs_create_link(&dev->driver->kobj, &dev->kobj,
kobject_name(&dev->kobj));
if (ret == 0) {
return ret;
}
+static void driver_sysfs_remove(struct device *dev)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (drv) {
+ sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj));
+ sysfs_remove_link(&dev->kobj, "driver");
+ }
+}
+
+/**
+ * device_bind_driver - bind a driver to one device.
+ * @dev: device.
+ *
+ * Allow manual attachment of a driver to a device.
+ * Caller must have already set @dev->driver.
+ *
+ * Note that this does not modify the bus reference count
+ * nor take the bus's rwsem. Please verify those are accounted
+ * for before calling this. (It is ok to call with no other effort
+ * from a driver's probe() method.)
+ *
+ * This function must be called with @dev->sem held.
+ */
+int device_bind_driver(struct device *dev)
+{
+ driver_bound(dev);
+ return driver_sysfs_add(dev);
+}
+
struct stupid_thread_structure {
struct device_driver *drv;
struct device *dev;
};
static atomic_t probe_count = ATOMIC_INIT(0);
+static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
+
static int really_probe(void *void_data)
{
struct stupid_thread_structure *data = void_data;
drv->bus->name, drv->name, dev->bus_id);
dev->driver = drv;
+ if (driver_sysfs_add(dev)) {
+ printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
+ __FUNCTION__, dev->bus_id);
+ goto probe_failed;
+ }
+
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
- if (ret) {
- dev->driver = NULL;
+ if (ret)
goto probe_failed;
- }
} else if (drv->probe) {
ret = drv->probe(dev);
- if (ret) {
- dev->driver = NULL;
+ if (ret)
goto probe_failed;
- }
- }
- if (device_bind_driver(dev)) {
- printk(KERN_ERR "%s: device_bind_driver(%s) failed\n",
- __FUNCTION__, dev->bus_id);
- /* How does undo a ->probe? We're screwed. */
}
+
+ driver_bound(dev);
ret = 1;
pr_debug("%s: Bound Device %s to Driver %s\n",
drv->bus->name, dev->bus_id, drv->name);
goto done;
probe_failed:
+ driver_sysfs_remove(dev);
+ dev->driver = NULL;
+
if (ret == -ENODEV || ret == -ENXIO) {
/* Driver matched, but didn't support device
* or device not found.
done:
kfree(data);
atomic_dec(&probe_count);
+ wake_up(&probe_waitqueue);
return ret;
}
struct task_struct *probe_task;
int ret = 0;
+ if (!device_is_registered(dev))
+ return -ENODEV;
if (drv->bus->match && !drv->bus->match(dev, drv))
goto done;
drv->bus->name, dev->bus_id, drv->name);
data = kmalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
data->drv = drv;
data->dev = dev;
probe_task = kthread_run(really_probe, data,
"probe-%s", dev->bus_id);
if (IS_ERR(probe_task))
- ret = PTR_ERR(probe_task);
+ ret = really_probe(data);
} else
ret = really_probe(data);
drv = dev->driver;
if (drv) {
get_driver(drv);
- sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj));
+ driver_sysfs_remove(dev);
sysfs_remove_link(&dev->kobj, "driver");
klist_remove(&dev->knode_driver);
+ if (dev->bus)
+ blocking_notifier_call_chain(&dev->bus->bus_notifier,
+ BUS_NOTIFY_UNBIND_DRIVER,
+ dev);
+
if (dev->bus && dev->bus->remove)
dev->bus->remove(dev);
else if (drv->remove)
}
}
+#ifdef CONFIG_PCI_MULTITHREAD_PROBE
+static int __init wait_for_probes(void)
+{
+ DEFINE_WAIT(wait);
+
+ printk(KERN_INFO "%s: waiting for %d threads\n", __FUNCTION__,
+ atomic_read(&probe_count));
+ if (!atomic_read(&probe_count))
+ return 0;
+ while (atomic_read(&probe_count)) {
+ prepare_to_wait(&probe_waitqueue, &wait, TASK_UNINTERRUPTIBLE);
+ if (atomic_read(&probe_count))
+ schedule();
+ }
+ finish_wait(&probe_waitqueue, &wait);
+ return 0;
+}
+
+core_initcall_sync(wait_for_probes);
+postcore_initcall_sync(wait_for_probes);
+arch_initcall_sync(wait_for_probes);
+subsys_initcall_sync(wait_for_probes);
+fs_initcall_sync(wait_for_probes);
+device_initcall_sync(wait_for_probes);
+late_initcall_sync(wait_for_probes);
+#endif
EXPORT_SYMBOL_GPL(device_bind_driver);
EXPORT_SYMBOL_GPL(device_release_driver);