From 0edb586049e57c56e625536476931117a57671e9 Mon Sep 17 00:00:00 2001 From: Cornelia Huck Date: Wed, 22 Jun 2005 16:59:51 +0200 Subject: [PATCH 1/5] [PATCH] driver core: add bus_find_device & driver_find_device functions Add bus_find_device() and driver_find_device() which allow searching for a device in the bus's resp. the driver's klist and obtain a reference on it. Signed-off-by: Cornelia Huck Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 34 ++++++++++++++++++++++++++++++++++ drivers/base/driver.c | 35 +++++++++++++++++++++++++++++++++++ include/linux/device.h | 5 +++++ 3 files changed, 74 insertions(+) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index c3fac7fd555e..2c64b792d074 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -177,6 +177,39 @@ int bus_for_each_dev(struct bus_type * bus, struct device * start, return error; } +/** + * bus_find_device - device iterator for locating a particular device. + * @bus: bus type + * @start: Device to begin with + * @data: Data to pass to match function + * @match: Callback function to check device + * + * This is similar to the bus_for_each_dev() function above, but it + * returns a reference to a device that is 'found' for later use, as + * determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero, this function will + * return to the caller and not iterate over any more devices. + */ +struct device * bus_find_device(struct bus_type *bus, + struct device *start, void *data, + int (*match)(struct device *, void *)) +{ + struct klist_iter i; + struct device *dev; + + if (!bus) + return NULL; + + klist_iter_init_node(&bus->klist_devices, &i, + (start ? &start->knode_bus : NULL)); + while ((dev = next_device(&i))) + if (match(dev, data) && get_device(dev)) + break; + klist_iter_exit(&i); + return dev; +} static struct device_driver * next_driver(struct klist_iter * i) @@ -557,6 +590,7 @@ int __init buses_init(void) EXPORT_SYMBOL_GPL(bus_for_each_dev); +EXPORT_SYMBOL_GPL(bus_find_device); EXPORT_SYMBOL_GPL(bus_for_each_drv); EXPORT_SYMBOL_GPL(bus_add_device); diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 1b645886e9eb..291c5954a3af 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -55,6 +55,41 @@ int driver_for_each_device(struct device_driver * drv, struct device * start, EXPORT_SYMBOL_GPL(driver_for_each_device); +/** + * driver_find_device - device iterator for locating a particular device. + * @driver: The device's driver + * @start: Device to begin with + * @data: Data to pass to match function + * @match: Callback function to check device + * + * This is similar to the driver_for_each_device() function above, but + * it returns a reference to a device that is 'found' for later use, as + * determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero, this function will + * return to the caller and not iterate over any more devices. + */ +struct device * driver_find_device(struct device_driver *drv, + struct device * start, void * data, + int (*match)(struct device *, void *)) +{ + struct klist_iter i; + struct device *dev; + + if (!drv) + return NULL; + + klist_iter_init_node(&drv->klist_devices, &i, + (start ? &start->knode_driver : NULL)); + while ((dev = next_device(&i))) + if (match(dev, data) && get_device(dev)) + break; + klist_iter_exit(&i); + return dev; +} +EXPORT_SYMBOL_GPL(driver_find_device); + /** * driver_create_file - create sysfs file for driver. * @drv: driver. diff --git a/include/linux/device.h b/include/linux/device.h index 7b781a72b293..07222c531d37 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -80,6 +80,8 @@ extern struct bus_type * find_bus(char * name); int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data, int (*fn)(struct device *, void *)); +struct device * bus_find_device(struct bus_type *bus, struct device *start, + void *data, int (*match)(struct device *, void *)); int bus_for_each_drv(struct bus_type * bus, struct device_driver * start, void * data, int (*fn)(struct device_driver *, void *)); @@ -142,6 +144,9 @@ extern void driver_remove_file(struct device_driver *, struct driver_attribute * extern int driver_for_each_device(struct device_driver * drv, struct device * start, void * data, int (*fn)(struct device *, void *)); +struct device * driver_find_device(struct device_driver *drv, + struct device *start, void *data, + int (*match)(struct device *, void *)); /* From 151ef38f7c0ec1b0420f04438b0316e3a30bf2e4 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 22 Jun 2005 16:09:05 -0700 Subject: [PATCH 2/5] [PATCH] driver core: Add the ability to unbind drivers to devices from userspace This adds a single file, "unbind", to the sysfs directory of every device that is currently bound to a driver. To unbind the driver from the device, write anything to this file and they will be disconnected from each other. Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 2c64b792d074..fa41ee90a53a 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -133,6 +133,34 @@ static struct kobj_type ktype_bus = { decl_subsys(bus, &ktype_bus, NULL); +/* Manually detach a device from it's associated driver. */ +static int driver_helper(struct device *dev, void *data) +{ + const char *name = data; + + if (strcmp(name, dev->bus_id) == 0) + return 1; + return 0; +} + +static ssize_t driver_unbind(struct device_driver *drv, + const char *buf, size_t count) +{ + struct bus_type *bus = get_bus(drv->bus); + struct device *dev; + int err = -ENODEV; + + dev = bus_find_device(bus, NULL, (void *)buf, driver_helper); + if ((dev) && + (dev->driver == drv)) { + device_release_driver(dev); + err = count; + } + return err; +} +static DRIVER_ATTR(unbind, S_IWUSR, NULL, driver_unbind); + + static struct device * next_device(struct klist_iter * i) { struct klist_node * n = klist_next(i); @@ -396,6 +424,7 @@ int bus_add_driver(struct device_driver * drv) module_add_driver(drv->owner, drv); driver_add_attrs(bus, drv); + driver_create_file(drv, &driver_attr_unbind); } return error; } @@ -413,6 +442,7 @@ int bus_add_driver(struct device_driver * drv) void bus_remove_driver(struct device_driver * drv) { if (drv->bus) { + driver_remove_file(drv, &driver_attr_unbind); driver_remove_attrs(drv->bus, drv); klist_remove(&drv->knode_bus); pr_debug("bus %s: remove driver %s\n", drv->bus->name, drv->name); From afdce75f1eaebcf358b7594ba7969aade105c3b0 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 22 Jun 2005 16:09:05 -0700 Subject: [PATCH 3/5] [PATCH] driver core: Add the ability to bind drivers to devices from userspace This adds a single file, "bind", to the sysfs directory of every driver registered with the driver core. To bind a device to a driver, write the bus id of the device you wish to bind to that specific driver to the "bind" file (remember to not add a trailing \n). If that bus id matches a device on that bus, and it does not currently have a driver bound to it, the probe sequence will be initiated with that driver and device. Note, this requires that the driver itself be willing and able to accept that device (usually through a device id type table). This patch does not make it possible to override the driver's id table. Signed-off-by: Greg Kroah-Hartman --- drivers/base/base.h | 1 + drivers/base/bus.c | 26 ++++++++++++++++++++++++++ drivers/base/dd.c | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index 645f62692920..783752b68a9a 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -5,6 +5,7 @@ extern int bus_add_driver(struct device_driver *); extern void bus_remove_driver(struct device_driver *); extern void driver_detach(struct device_driver * drv); +extern int driver_probe_device(struct device_driver *, struct device *); static inline struct class_device *to_class_dev(struct kobject *obj) { diff --git a/drivers/base/bus.c b/drivers/base/bus.c index fa41ee90a53a..7e17488271a8 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -160,6 +160,30 @@ static ssize_t driver_unbind(struct device_driver *drv, } static DRIVER_ATTR(unbind, S_IWUSR, NULL, driver_unbind); +/* + * Manually attach a device to a driver. + * Note: the driver must want to bind to the device, + * it is not possible to override the driver's id table. + */ +static ssize_t driver_bind(struct device_driver *drv, + const char *buf, size_t count) +{ + struct bus_type *bus = get_bus(drv->bus); + struct device *dev; + int err = -ENODEV; + + dev = bus_find_device(bus, NULL, (void *)buf, driver_helper); + if ((dev) && + (dev->driver == NULL)) { + down(&dev->sem); + err = driver_probe_device(drv, dev); + up(&dev->sem); + put_device(dev); + } + return err; +} +static DRIVER_ATTR(bind, S_IWUSR, NULL, driver_bind); + static struct device * next_device(struct klist_iter * i) { @@ -425,6 +449,7 @@ int bus_add_driver(struct device_driver * drv) driver_add_attrs(bus, drv); driver_create_file(drv, &driver_attr_unbind); + driver_create_file(drv, &driver_attr_bind); } return error; } @@ -442,6 +467,7 @@ int bus_add_driver(struct device_driver * drv) void bus_remove_driver(struct device_driver * drv) { if (drv->bus) { + driver_remove_file(drv, &driver_attr_bind); driver_remove_file(drv, &driver_attr_unbind); driver_remove_attrs(drv->bus, drv); klist_remove(&drv->knode_bus); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 6db3a789c54f..16323f9cbff0 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -65,7 +65,7 @@ void device_bind_driver(struct device * dev) * * This function must be called with @dev->sem held. */ -static int driver_probe_device(struct device_driver * drv, struct device * dev) +int driver_probe_device(struct device_driver * drv, struct device * dev) { int ret = 0; From 23d3d602cb96addd3c1158424fb01a49ea5e81b1 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 22 Jun 2005 16:09:05 -0700 Subject: [PATCH 4/5] [PATCH] driver core: change bus_rescan_devices to return void No one was looking at the return value of bus_rescan_devices, and it really wasn't anything that anyone in the kernel would ever care about. So change it which enabled some counting code to be removed also. Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 27 +++++++++------------------ include/linux/device.h | 2 +- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 7e17488271a8..96fe2f956754 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -483,31 +483,22 @@ void bus_remove_driver(struct device_driver * drv) /* Helper for bus_rescan_devices's iter */ static int bus_rescan_devices_helper(struct device *dev, void *data) { - int *count = data; - - if (!dev->driver && (device_attach(dev) > 0)) - (*count)++; - + if (!dev->driver) + device_attach(dev); return 0; } - /** - * bus_rescan_devices - rescan devices on the bus for possible drivers - * @bus: the bus to scan. + * bus_rescan_devices - rescan devices on the bus for possible drivers + * @bus: the bus to scan. * - * This function will look for devices on the bus with no driver - * attached and rescan it against existing drivers to see if it - * matches any. Calls device_attach(). Returns the number of devices - * that were sucessfully bound to a driver. + * This function will look for devices on the bus with no driver + * attached and rescan it against existing drivers to see if it matches + * any by calling device_attach() for the unbound devices. */ -int bus_rescan_devices(struct bus_type * bus) +void bus_rescan_devices(struct bus_type * bus) { - int count = 0; - - bus_for_each_dev(bus, NULL, &count, bus_rescan_devices_helper); - - return count; + bus_for_each_dev(bus, NULL, NULL, bus_rescan_devices_helper); } diff --git a/include/linux/device.h b/include/linux/device.h index 07222c531d37..f378c846e6d5 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -69,7 +69,7 @@ struct bus_type { extern int bus_register(struct bus_type * bus); extern void bus_unregister(struct bus_type * bus); -extern int bus_rescan_devices(struct bus_type * bus); +extern void bus_rescan_devices(struct bus_type * bus); extern struct bus_type * get_bus(struct bus_type * bus); extern void put_bus(struct bus_type * bus); From d62c0f9fd2d3943a3eca85b490d86e1605000ccb Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Fri, 24 Jun 2005 08:39:33 -0700 Subject: [PATCH 5/5] [PATCH] Driver core: Use klist_del() instead of klist_remove(). Use klist_del() instead of klist_remove() when unregistering devices. This will prevent a deadlock when executing a recursive unregister using device_for_each_child(). Signed-off-by Patrick Mochel Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/core.c b/drivers/base/core.c index 86d79755fbfb..efe03a024a5b 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -333,7 +333,7 @@ void device_del(struct device * dev) struct device * parent = dev->parent; if (parent) - klist_remove(&dev->knode_parent); + klist_del(&dev->knode_parent); /* Notify the platform of the removal, in case they * need to do anything...