summaryrefslogtreecommitdiff
path: root/drivers/base
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@g5.osdl.org>2006-09-26 11:49:46 -0700
committerLinus Torvalds <torvalds@g5.osdl.org>2006-09-26 11:49:46 -0700
commitdd77a4ee0f3981693d4229aa1d57cea9e526ff47 (patch)
treecb486be20b950201103a03636cbb1e1d180f0098 /drivers/base
parente8216dee838c09776680a6f1a2e54d81f3cdfa14 (diff)
parent7e9f4b2d3e21e87c26025810413ef1592834e63b (diff)
downloadlinux-3.10-dd77a4ee0f3981693d4229aa1d57cea9e526ff47.tar.gz
linux-3.10-dd77a4ee0f3981693d4229aa1d57cea9e526ff47.tar.bz2
linux-3.10-dd77a4ee0f3981693d4229aa1d57cea9e526ff47.zip
Merge master.kernel.org:/pub/scm/linux/kernel/git/gregkh/driver-2.6
* master.kernel.org:/pub/scm/linux/kernel/git/gregkh/driver-2.6: (47 commits) Driver core: Don't call put methods while holding a spinlock Driver core: Remove unneeded routines from driver core Driver core: Fix potential deadlock in driver core PCI: enable driver multi-threaded probe Driver Core: add ability for drivers to do a threaded probe sysfs: add proper sysfs_init() prototype drivers/base: check errors drivers/base: Platform notify needs to occur before drivers attach to the device v4l-dev2: handle __must_check add CONFIG_ENABLE_MUST_CHECK add __must_check to device management code Driver core: fixed add_bind_files() definition Driver core: fix comments in drivers/base/power/resume.c sysfs_remove_bin_file: no return value, dump_stack on error kobject: must_check fixes Driver core: add ability for devices to create and remove bin files Class: add support for class interfaces for devices Driver core: create devices/virtual/ tree Driver core: add device_rename function Driver core: add ability for classes to handle devices properly ...
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/base.h2
-rw-r--r--drivers/base/bus.c133
-rw-r--r--drivers/base/class.c34
-rw-r--r--drivers/base/core.c230
-rw-r--r--drivers/base/dd.c147
-rw-r--r--drivers/base/driver.c16
-rw-r--r--drivers/base/platform.c30
-rw-r--r--drivers/base/power/resume.c37
-rw-r--r--drivers/base/power/suspend.c92
-rw-r--r--drivers/base/power/sysfs.c35
10 files changed, 577 insertions, 179 deletions
diff --git a/drivers/base/base.h b/drivers/base/base.h
index c3b8dc98b8a..d26644a5953 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -16,7 +16,7 @@ extern int cpu_dev_init(void);
extern int attribute_container_init(void);
extern int bus_add_device(struct device * dev);
-extern void bus_attach_device(struct device * dev);
+extern int bus_attach_device(struct device * dev);
extern void bus_remove_device(struct device * dev);
extern struct bus_type *get_bus(struct bus_type * bus);
extern void put_bus(struct bus_type * bus);
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index 2e954d07175..12173d16bea 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -371,12 +371,20 @@ int bus_add_device(struct device * dev)
if (bus) {
pr_debug("bus %s: add device %s\n", bus->name, dev->bus_id);
error = device_add_attrs(bus, dev);
- if (!error) {
- sysfs_create_link(&bus->devices.kobj, &dev->kobj, dev->bus_id);
- sysfs_create_link(&dev->kobj, &dev->bus->subsys.kset.kobj, "subsystem");
- sysfs_create_link(&dev->kobj, &dev->bus->subsys.kset.kobj, "bus");
- }
+ if (error)
+ goto out;
+ error = sysfs_create_link(&bus->devices.kobj,
+ &dev->kobj, dev->bus_id);
+ if (error)
+ goto out;
+ error = sysfs_create_link(&dev->kobj,
+ &dev->bus->subsys.kset.kobj, "subsystem");
+ if (error)
+ goto out;
+ error = sysfs_create_link(&dev->kobj,
+ &dev->bus->subsys.kset.kobj, "bus");
}
+out:
return error;
}
@@ -384,16 +392,24 @@ int bus_add_device(struct device * dev)
* bus_attach_device - add device to bus
* @dev: device tried to attach to a driver
*
+ * - Add device to bus's list of devices.
* - Try to attach to driver.
*/
-void bus_attach_device(struct device * dev)
+int bus_attach_device(struct device * dev)
{
- struct bus_type * bus = dev->bus;
+ struct bus_type *bus = dev->bus;
+ int ret = 0;
if (bus) {
- device_attach(dev);
- klist_add_tail(&dev->knode_bus, &bus->klist_devices);
+ dev->is_registered = 1;
+ ret = device_attach(dev);
+ if (ret >= 0) {
+ klist_add_tail(&dev->knode_bus, &bus->klist_devices);
+ ret = 0;
+ } else
+ dev->is_registered = 0;
}
+ return ret;
}
/**
@@ -412,7 +428,8 @@ void bus_remove_device(struct device * dev)
sysfs_remove_link(&dev->kobj, "bus");
sysfs_remove_link(&dev->bus->devices.kobj, dev->bus_id);
device_remove_attrs(dev->bus, dev);
- klist_remove(&dev->knode_bus);
+ dev->is_registered = 0;
+ klist_del(&dev->knode_bus);
pr_debug("bus %s: remove device %s\n", dev->bus->name, dev->bus_id);
device_release_driver(dev);
put_bus(dev->bus);
@@ -455,10 +472,17 @@ static void driver_remove_attrs(struct bus_type * bus, struct device_driver * dr
* Thanks to drivers making their tables __devinit, we can't allow manual
* bind and unbind from userspace unless CONFIG_HOTPLUG is enabled.
*/
-static void add_bind_files(struct device_driver *drv)
+static int __must_check add_bind_files(struct device_driver *drv)
{
- driver_create_file(drv, &driver_attr_unbind);
- driver_create_file(drv, &driver_attr_bind);
+ int ret;
+
+ ret = driver_create_file(drv, &driver_attr_unbind);
+ if (ret == 0) {
+ ret = driver_create_file(drv, &driver_attr_bind);
+ if (ret)
+ driver_remove_file(drv, &driver_attr_unbind);
+ }
+ return ret;
}
static void remove_bind_files(struct device_driver *drv)
@@ -467,7 +491,7 @@ static void remove_bind_files(struct device_driver *drv)
driver_remove_file(drv, &driver_attr_unbind);
}
#else
-static inline void add_bind_files(struct device_driver *drv) {}
+static inline int add_bind_files(struct device_driver *drv) { return 0; }
static inline void remove_bind_files(struct device_driver *drv) {}
#endif
@@ -476,7 +500,7 @@ static inline void remove_bind_files(struct device_driver *drv) {}
* @drv: driver.
*
*/
-int bus_add_driver(struct device_driver * drv)
+int bus_add_driver(struct device_driver *drv)
{
struct bus_type * bus = get_bus(drv->bus);
int error = 0;
@@ -484,27 +508,39 @@ int bus_add_driver(struct device_driver * drv)
if (bus) {
pr_debug("bus %s: add driver %s\n", bus->name, drv->name);
error = kobject_set_name(&drv->kobj, "%s", drv->name);
- if (error) {
- put_bus(bus);
- return error;
- }
+ if (error)
+ goto out_put_bus;
drv->kobj.kset = &bus->drivers;
- if ((error = kobject_register(&drv->kobj))) {
- put_bus(bus);
- return error;
- }
+ if ((error = kobject_register(&drv->kobj)))
+ goto out_put_bus;
- driver_attach(drv);
+ error = driver_attach(drv);
+ if (error)
+ goto out_unregister;
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
module_add_driver(drv->owner, drv);
- driver_add_attrs(bus, drv);
- add_bind_files(drv);
+ error = driver_add_attrs(bus, drv);
+ if (error) {
+ /* How the hell do we get out of this pickle? Give up */
+ printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
+ __FUNCTION__, drv->name);
+ }
+ error = add_bind_files(drv);
+ if (error) {
+ /* Ditto */
+ printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
+ __FUNCTION__, drv->name);
+ }
}
return error;
+out_unregister:
+ kobject_unregister(&drv->kobj);
+out_put_bus:
+ put_bus(bus);
+ return error;
}
-
/**
* bus_remove_driver - delete driver from bus's knowledge.
* @drv: driver.
@@ -530,16 +566,21 @@ 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)
+static int __must_check bus_rescan_devices_helper(struct device *dev,
+ void *data)
{
+ int ret = 0;
+
if (!dev->driver) {
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
- device_attach(dev);
+ ret = device_attach(dev);
if (dev->parent)
up(&dev->parent->sem);
+ if (ret > 0)
+ ret = 0;
}
- return 0;
+ return ret < 0 ? ret : 0;
}
/**
@@ -550,9 +591,9 @@ static int bus_rescan_devices_helper(struct device *dev, void *data)
* attached and rescan it against existing drivers to see if it matches
* any by calling device_attach() for the unbound devices.
*/
-void bus_rescan_devices(struct bus_type * bus)
+int bus_rescan_devices(struct bus_type * bus)
{
- bus_for_each_dev(bus, NULL, NULL, bus_rescan_devices_helper);
+ return bus_for_each_dev(bus, NULL, NULL, bus_rescan_devices_helper);
}
/**
@@ -564,7 +605,7 @@ void bus_rescan_devices(struct bus_type * bus)
* to use if probing criteria changed during a devices lifetime and
* driver attachment should change accordingly.
*/
-void device_reprobe(struct device *dev)
+int device_reprobe(struct device *dev)
{
if (dev->driver) {
if (dev->parent) /* Needed for USB */
@@ -573,14 +614,14 @@ void device_reprobe(struct device *dev)
if (dev->parent)
up(&dev->parent->sem);
}
-
- bus_rescan_devices_helper(dev, NULL);
+ return bus_rescan_devices_helper(dev, NULL);
}
EXPORT_SYMBOL_GPL(device_reprobe);
-struct bus_type * get_bus(struct bus_type * bus)
+struct bus_type *get_bus(struct bus_type *bus)
{
- return bus ? container_of(subsys_get(&bus->subsys), struct bus_type, subsys) : NULL;
+ return bus ? container_of(subsys_get(&bus->subsys),
+ struct bus_type, subsys) : NULL;
}
void put_bus(struct bus_type * bus)
@@ -655,22 +696,6 @@ static void klist_devices_put(struct klist_node *n)
put_device(dev);
}
-static void klist_drivers_get(struct klist_node *n)
-{
- struct device_driver *drv = container_of(n, struct device_driver,
- knode_bus);
-
- get_driver(drv);
-}
-
-static void klist_drivers_put(struct klist_node *n)
-{
- struct device_driver *drv = container_of(n, struct device_driver,
- knode_bus);
-
- put_driver(drv);
-}
-
/**
* bus_register - register a bus with the system.
* @bus: bus.
@@ -706,7 +731,7 @@ int bus_register(struct bus_type * bus)
goto bus_drivers_fail;
klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put);
- klist_init(&bus->klist_drivers, klist_drivers_get, klist_drivers_put);
+ klist_init(&bus->klist_drivers, NULL, NULL);
bus_add_attrs(bus);
pr_debug("bus type '%s' registered\n", bus->name);
diff --git a/drivers/base/class.c b/drivers/base/class.c
index de8908320f2..b06b0e2b9c6 100644
--- a/drivers/base/class.c
+++ b/drivers/base/class.c
@@ -19,6 +19,8 @@
#include <linux/slab.h>
#include "base.h"
+extern struct subsystem devices_subsys;
+
#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)
#define to_class(obj) container_of(obj, struct class, subsys.kset.kobj)
@@ -197,7 +199,7 @@ static int class_device_create_uevent(struct class_device *class_dev,
* Note, the pointer created here is to be destroyed when finished by
* making a call to class_destroy().
*/
-struct class *class_create(struct module *owner, char *name)
+struct class *class_create(struct module *owner, const char *name)
{
struct class *cls;
int retval;
@@ -361,7 +363,7 @@ static int class_uevent(struct kset *kset, struct kobject *kobj, char **envp,
pr_debug("%s - name = %s\n", __FUNCTION__, class_dev->class_id);
if (class_dev->dev) {
- /* add physical device, backing this device */
+ /* add device, backing this class device (deprecated) */
struct device *dev = class_dev->dev;
char *path = kobject_get_path(&dev->kobj, GFP_KERNEL);
@@ -679,7 +681,8 @@ int class_device_register(struct class_device *class_dev)
struct class_device *class_device_create(struct class *cls,
struct class_device *parent,
dev_t devt,
- struct device *device, char *fmt, ...)
+ struct device *device,
+ const char *fmt, ...)
{
va_list args;
struct class_device *class_dev = NULL;
@@ -839,6 +842,7 @@ int class_interface_register(struct class_interface *class_intf)
{
struct class *parent;
struct class_device *class_dev;
+ struct device *dev;
if (!class_intf || !class_intf->class)
return -ENODEV;
@@ -853,6 +857,10 @@ int class_interface_register(struct class_interface *class_intf)
list_for_each_entry(class_dev, &parent->children, node)
class_intf->add(class_dev, class_intf);
}
+ if (class_intf->add_dev) {
+ list_for_each_entry(dev, &parent->devices, node)
+ class_intf->add_dev(dev, class_intf);
+ }
up(&parent->sem);
return 0;
@@ -862,6 +870,7 @@ void class_interface_unregister(struct class_interface *class_intf)
{
struct class * parent = class_intf->class;
struct class_device *class_dev;
+ struct device *dev;
if (!parent)
return;
@@ -872,12 +881,31 @@ void class_interface_unregister(struct class_interface *class_intf)
list_for_each_entry(class_dev, &parent->children, node)
class_intf->remove(class_dev, class_intf);
}
+ if (class_intf->remove_dev) {
+ list_for_each_entry(dev, &parent->devices, node)
+ class_intf->remove_dev(dev, class_intf);
+ }
up(&parent->sem);
class_put(parent);
}
+int virtual_device_parent(struct device *dev)
+{
+ if (!dev->class)
+ return -ENODEV;
+
+ if (!dev->class->virtual_dir) {
+ static struct kobject *virtual_dir = NULL;
+
+ if (!virtual_dir)
+ virtual_dir = kobject_add_dir(&devices_subsys.kset.kobj, "virtual");
+ dev->class->virtual_dir = kobject_add_dir(virtual_dir, dev->class->name);
+ }
+ dev->kobj.parent = dev->class->virtual_dir;
+ return 0;
+}
int __init classes_init(void)
{
diff --git a/drivers/base/core.c b/drivers/base/core.c
index be6b5bc0677..b224bb43ff6 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -3,6 +3,8 @@
*
* Copyright (c) 2002-3 Patrick Mochel
* Copyright (c) 2002-3 Open Source Development Labs
+ * Copyright (c) 2006 Greg Kroah-Hartman <gregkh@suse.de>
+ * Copyright (c) 2006 Novell, Inc.
*
* This file is released under the GPLv2
*
@@ -92,6 +94,8 @@ static void device_release(struct kobject * kobj)
if (dev->release)
dev->release(dev);
+ else if (dev->class && dev->class->dev_release)
+ dev->class->dev_release(dev);
else {
printk(KERN_ERR "Device '%s' does not have a release() function, "
"it is broken and must be fixed.\n",
@@ -149,17 +153,21 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp,
"MINOR=%u", MINOR(dev->devt));
}
- /* add bus name of physical device */
+ /* add bus name (same as SUBSYSTEM, deprecated) */
if (dev->bus)
add_uevent_var(envp, num_envp, &i,
buffer, buffer_size, &length,
"PHYSDEVBUS=%s", dev->bus->name);
- /* add driver name of physical device */
- if (dev->driver)
+ /* add driver name (PHYSDEV* values are deprecated)*/
+ if (dev->driver) {
+ add_uevent_var(envp, num_envp, &i,
+ buffer, buffer_size, &length,
+ "DRIVER=%s", dev->driver->name);
add_uevent_var(envp, num_envp, &i,
buffer, buffer_size, &length,
"PHYSDEVDRIVER=%s", dev->driver->name);
+ }
/* terminate, set to next free slot, shrink available space */
envp[i] = NULL;
@@ -177,6 +185,15 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp,
}
}
+ if (dev->class && dev->class->dev_uevent) {
+ /* have the class specific function add its stuff */
+ retval = dev->class->dev_uevent(dev, envp, num_envp, buffer, buffer_size);
+ if (retval) {
+ pr_debug("%s - dev_uevent() returned %d\n",
+ __FUNCTION__, retval);
+ }
+ }
+
return retval;
}
@@ -193,6 +210,72 @@ static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
return count;
}
+static int device_add_groups(struct device *dev)
+{
+ int i;
+ int error = 0;
+
+ if (dev->groups) {
+ for (i = 0; dev->groups[i]; i++) {
+ error = sysfs_create_group(&dev->kobj, dev->groups[i]);
+ if (error) {
+ while (--i >= 0)
+ sysfs_remove_group(&dev->kobj, dev->groups[i]);
+ goto out;
+ }
+ }
+ }
+out:
+ return error;
+}
+
+static void device_remove_groups(struct device *dev)
+{
+ int i;
+ if (dev->groups) {
+ for (i = 0; dev->groups[i]; i++) {
+ sysfs_remove_group(&dev->kobj, dev->groups[i]);
+ }
+ }
+}
+
+static int device_add_attrs(struct device *dev)
+{
+ struct class *class = dev->class;
+ int error = 0;
+ int i;
+
+ if (!class)
+ return 0;
+
+ if (class->dev_attrs) {
+ for (i = 0; attr_name(class->dev_attrs[i]); i++) {
+ error = device_create_file(dev, &class->dev_attrs[i]);
+ if (error)
+ break;
+ }
+ }
+ if (error)
+ while (--i >= 0)
+ device_remove_file(dev, &class->dev_attrs[i]);
+ return error;
+}
+
+static void device_remove_attrs(struct device *dev)
+{
+ struct class *class = dev->class;
+ int i;
+
+ if (!class)
+ return;
+
+ if (class->dev_attrs) {
+ for (i = 0; attr_name(class->dev_attrs[i]); i++)
+ device_remove_file(dev, &class->dev_attrs[i]);
+ }
+}
+
+
static ssize_t show_dev(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -236,6 +319,32 @@ void device_remove_file(struct device * dev, struct device_attribute * attr)
}
}
+/**
+ * device_create_bin_file - create sysfs binary attribute file for device.
+ * @dev: device.
+ * @attr: device binary attribute descriptor.
+ */
+int device_create_bin_file(struct device *dev, struct bin_attribute *attr)
+{
+ int error = -EINVAL;
+ if (dev)
+ error = sysfs_create_bin_file(&dev->kobj, attr);
+ return error;
+}
+EXPORT_SYMBOL_GPL(device_create_bin_file);
+
+/**
+ * device_remove_bin_file - remove sysfs binary attribute file
+ * @dev: device.
+ * @attr: device binary attribute descriptor.
+ */
+void device_remove_bin_file(struct device *dev, struct bin_attribute *attr)
+{
+ if (dev)
+ sysfs_remove_bin_file(&dev->kobj, attr);
+}
+EXPORT_SYMBOL_GPL(device_remove_bin_file);
+
static void klist_children_get(struct klist_node *n)
{
struct device *dev = container_of(n, struct device, knode_parent);
@@ -289,12 +398,20 @@ int device_add(struct device *dev)
{
struct device *parent = NULL;
char *class_name = NULL;
+ struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev || !strlen(dev->bus_id))
goto Error;
+ /* if this is a class device, and has no parent, create one */
+ if ((dev->class) && (dev->parent == NULL)) {
+ error = virtual_device_parent(dev);
+ if (error)
+ goto Error;
+ }
+
parent = get_device(dev->parent);
pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);
@@ -307,6 +424,10 @@ int device_add(struct device *dev)
if ((error = kobject_add(&dev->kobj)))
goto Error;
+ /* notify platform of device entry */
+ if (platform_notify)
+ platform_notify(dev);
+
dev->uevent_attr.attr.name = "uevent";
dev->uevent_attr.attr.mode = S_IWUSR;
if (dev->driver)
@@ -340,12 +461,17 @@ int device_add(struct device *dev)
"subsystem");
sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,
dev->bus_id);
-
- sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
- class_name = make_class_name(dev->class->name, &dev->kobj);
- sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
+ if (parent) {
+ sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
+ class_name = make_class_name(dev->class->name, &dev->kobj);
+ sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
+ }
}
+ if ((error = device_add_attrs(dev)))
+ goto AttrsError;
+ if ((error = device_add_groups(dev)))
+ goto GroupError;
if ((error = device_pm_add(dev)))
goto PMError;
if ((error = bus_add_device(dev)))
@@ -356,15 +482,16 @@ int device_add(struct device *dev)
klist_add_tail(&dev->knode_parent, &parent->klist_children);
if (dev->class) {
- /* tie the class to the device */
down(&dev->class->sem);
+ /* tie the class to the device */
list_add_tail(&dev->node, &dev->class->devices);
+
+ /* notify any interfaces that the device is here */
+ list_for_each_entry(class_intf, &dev->class->interfaces, node)
+ if (class_intf->add_dev)
+ class_intf->add_dev(dev, class_intf);
up(&dev->class->sem);
}
-
- /* notify platform of device entry */
- if (platform_notify)
- platform_notify(dev);
Done:
kfree(class_name);
put_device(dev);
@@ -372,6 +499,10 @@ int device_add(struct device *dev)
BusError:
device_pm_remove(dev);
PMError:
+ device_remove_groups(dev);
+ GroupError:
+ device_remove_attrs(dev);
+ AttrsError:
if (dev->devt_attr) {
device_remove_file(dev, dev->devt_attr);
kfree(dev->devt_attr);
@@ -449,6 +580,7 @@ void device_del(struct device * dev)
{
struct device * parent = dev->parent;
char *class_name = NULL;
+ struct class_interface *class_intf;
if (parent)
klist_del(&dev->knode_parent);
@@ -458,14 +590,23 @@ void device_del(struct device * dev)
sysfs_remove_link(&dev->kobj, "subsystem");
sysfs_remove_link(&dev->class->subsys.kset.kobj, dev->bus_id);
class_name = make_class_name(dev->class->name, &dev->kobj);
- sysfs_remove_link(&dev->kobj, "device");
- sysfs_remove_link(&dev->parent->kobj, class_name);
+ if (parent) {
+ sysfs_remove_link(&dev->kobj, "device");
+ sysfs_remove_link(&dev->parent->kobj, class_name);
+ }
kfree(class_name);
down(&dev->class->sem);
+ /* notify any interfaces that the device is now gone */
+ list_for_each_entry(class_intf, &dev->class->interfaces, node)
+ if (class_intf->remove_dev)
+ class_intf->remove_dev(dev, class_intf);
+ /* remove the device from the class list */
list_del_init(&dev->node);
up(&dev->class->sem);
}
device_remove_file(dev, &dev->uevent_attr);
+ device_remove_groups(dev);
+ device_remove_attrs(dev);
/* Notify the platform of the removal, in case they
* need to do anything...
@@ -579,7 +720,7 @@ static void device_create_release(struct device *dev)
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
- dev_t devt, char *fmt, ...)
+ dev_t devt, const char *fmt, ...)
{
va_list args;
struct device *dev = NULL;
@@ -587,10 +728,6 @@ struct device *device_create(struct class *class, struct device *parent,
if (class == NULL || IS_ERR(class))
goto error;
- if (parent == NULL) {
- printk(KERN_WARNING "%s does not work yet for NULL parents\n", __FUNCTION__);
- goto error;
- }
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
@@ -644,3 +781,58 @@ void device_destroy(struct class *class, dev_t devt)
device_unregister(dev);
}
EXPORT_SYMBOL_GPL(device_destroy);
+
+/**
+ * device_rename - renames a device
+ * @dev: the pointer to the struct device to be renamed
+ * @new_name: the new name of the device
+ */
+int device_rename(struct device *dev, char *new_name)
+{
+ char *old_class_name = NULL;
+ char *new_class_name = NULL;
+ char *old_symlink_name = NULL;
+ int error;
+
+ dev = get_device(dev);
+ if (!dev)
+ return -EINVAL;
+
+ pr_debug("DEVICE: renaming '%s' to '%s'\n", dev->bus_id, new_name);
+
+ if ((dev->class) && (dev->parent))
+ old_class_name = make_class_name(dev->class->name, &dev->kobj);
+
+ if (dev->class) {
+ old_symlink_name = kmalloc(BUS_ID_SIZE, GFP_KERNEL);
+ if (!old_symlink_name)
+ return -ENOMEM;
+ strlcpy(old_symlink_name, dev->bus_id, BUS_ID_SIZE);
+ }
+
+ strlcpy(dev->bus_id, new_name, BUS_ID_SIZE);
+
+ error = kobject_rename(&dev->kobj, new_name);
+
+ if (old_class_name) {
+ new_class_name = make_class_name(dev->class->name, &dev->kobj);
+ if (new_class_name) {
+ sysfs_create_link(&dev->parent->kobj, &dev->kobj,
+ new_class_name);
+ sysfs_remove_link(&dev->parent->kobj, old_class_name);
+ }
+ }
+ if (dev->class) {
+ sysfs_remove_link(&dev->class->subsys.kset.kobj,
+ old_symlink_name);
+ sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,
+ dev->bus_id);
+ }
+ put_device(dev);
+
+ kfree(old_class_name);
+ kfree(new_class_name);
+ kfree(old_symlink_name);
+
+ return error;
+}
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index 889c7111123..b5f43c3e44f 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -17,6 +17,7 @@
#include <linux/device.h>
#include <linux/module.h>
+#include <linux/kthread.h>
#include "base.h"
#include "power/power.h"
@@ -38,66 +39,73 @@
*
* This function must be called with @dev->sem held.
*/
-void device_bind_driver(struct device * dev)
+int device_bind_driver(struct device *dev)
{
- if (klist_node_attached(&dev->knode_driver))
- return;
+ 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;
+ }
pr_debug("bound device '%s' to driver '%s'\n",
dev->bus_id, dev->driver->name);
klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices);
- sysfs_create_link(&dev->driver->kobj, &dev->kobj,
+ ret = sysfs_create_link(&dev->driver->kobj, &dev->kobj,
kobject_name(&dev->kobj));
- sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver");
+ if (ret == 0) {
+ ret = sysfs_create_link(&dev->kobj, &dev->driver->kobj,
+ "driver");
+ if (ret)
+ sysfs_remove_link(&dev->driver->kobj,
+ kobject_name(&dev->kobj));
+ }
+ return ret;
}
-/**
- * driver_probe_device - attempt to bind device & driver.
- * @drv: driver.
- * @dev: device.
- *
- * First, we call the bus's match function, if one present, which
- * should compare the device IDs the driver supports with the
- * device IDs of the device. Note we don't do this ourselves
- * because we don't know the format of the ID structures, nor what
- * is to be considered a match and what is not.
- *
- * This function returns 1 if a match is found, an error if one
- * occurs (that is not -ENODEV or -ENXIO), and 0 otherwise.
- *
- * This function must be called with @dev->sem held. When called
- * for a USB interface, @dev->parent->sem must be held as well.
- */
-int driver_probe_device(struct device_driver * drv, struct device * dev)
+struct stupid_thread_structure {
+ struct device_driver *drv;
+ struct device *dev;
+};
+
+static atomic_t probe_count = ATOMIC_INIT(0);
+static int really_probe(void *void_data)
{
+ struct stupid_thread_structure *data = void_data;
+ struct device_driver *drv = data->drv;
+ struct device *dev = data->dev;
int ret = 0;
- if (drv->bus->match && !drv->bus->match(dev, drv))
- goto Done;
+ atomic_inc(&probe_count);
+ pr_debug("%s: Probing driver %s with device %s\n",
+ drv->bus->name, drv->name, dev->bus_id);
- pr_debug("%s: Matched Device %s with Driver %s\n",
- drv->bus->name, dev->bus_id, drv->name);
dev->driver = drv;
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret) {
dev->driver = NULL;
- goto ProbeFailed;
+ goto probe_failed;
}
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret) {
dev->driver = NULL;
- goto ProbeFailed;
+ goto probe_failed;
}
}
- device_bind_driver(dev);
+ 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. */
+ }
ret = 1;
pr_debug("%s: Bound Device %s to Driver %s\n",
drv->bus->name, dev->bus_id, drv->name);
- goto Done;
+ goto done;
- ProbeFailed:
+probe_failed:
if (ret == -ENODEV || ret == -ENXIO) {
/* Driver matched, but didn't support device
* or device not found.
@@ -110,7 +118,71 @@ int driver_probe_device(struct device_driver * drv, struct device * dev)
"%s: probe of %s failed with error %d\n",
drv->name, dev->bus_id, ret);
}
- Done:
+done:
+ kfree(data);
+ atomic_dec(&probe_count);
+ return ret;
+}
+
+/**
+ * driver_probe_done
+ * Determine if the probe sequence is finished or not.
+ *
+ * Should somehow figure out how to use a semaphore, not an atomic variable...
+ */
+int driver_probe_done(void)
+{
+ pr_debug("%s: probe_count = %d\n", __FUNCTION__,
+ atomic_read(&probe_count));
+ if (atomic_read(&probe_count))
+ return -EBUSY;
+ return 0;
+}
+
+/**
+ * driver_probe_device - attempt to bind device & driver together
+ * @drv: driver to bind a device to
+ * @dev: device to try to bind to the driver
+ *
+ * First, we call the bus's match function, if one present, which should
+ * compare the device IDs the driver supports with the device IDs of the
+ * device. Note we don't do this ourselves because we don't know the
+ * format of the ID structures, nor what is to be considered a match and
+ * what is not.
+ *
+ * This function returns 1 if a match is found, an error if one occurs
+ * (that is not -ENODEV or -ENXIO), and 0 otherwise.
+ *
+ * This function must be called with @dev->sem held. When called for a
+ * USB interface, @dev->parent->sem must be held as well.
+ */
+int driver_probe_device(struct device_driver * drv, struct device * dev)
+{
+ struct stupid_thread_structure *data;
+ 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;
+
+ pr_debug("%s: Matched Device %s with Driver %s\n",
+ drv->bus->name, dev->bus_id, drv->name);
+
+ data = kmalloc(sizeof(*data), GFP_KERNEL);
+ data->drv = drv;
+ data->dev = dev;
+
+ if (drv->multithread_probe) {
+ probe_task = kthread_run(really_probe, data,
+ "probe-%s", dev->bus_id);
+ if (IS_ERR(probe_task))
+ ret = PTR_ERR(probe_task);
+ } else
+ ret = really_probe(data);
+
+done:
return ret;
}
@@ -139,8 +211,9 @@ int device_attach(struct device * dev)
down(&dev->sem);
if (dev->driver) {
- device_bind_driver(dev);
- ret = 1;
+ ret = device_bind_driver(dev);
+ if (ret == 0)
+ ret = 1;
} else
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
up(&dev->sem);
@@ -182,9 +255,9 @@ static int __driver_attach(struct device * dev, void * data)
* returns 0 and the @dev->driver is set, we've found a
* compatible pair.
*/
-void driver_attach(struct device_driver * drv)
+int driver_attach(struct device_driver * drv)
{
- bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
+ return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
/**
diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index 562600dd540..1214cbd17d8 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -142,20 +142,6 @@ void put_driver(struct device_driver * drv)
kobject_put(&drv->kobj);
}
-static void klist_devices_get(struct klist_node *n)
-{
- struct device *dev = container_of(n, struct device, knode_driver);
-
- get_device(dev);
-}
-
-static void klist_devices_put(struct klist_node *n)
-{
- struct device *dev = container_of(n, struct device, knode_driver);
-
- put_device(dev);
-}
-
/**
* driver_register - register driver with bus
* @drv: driver to register
@@ -175,7 +161,7 @@ int driver_register(struct device_driver * drv)
(drv->bus->shutdown && drv->shutdown)) {
printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name);
}
- klist_init(&drv->klist_devices, klist_devices_get, klist_devices_put);
+ klist_init(&drv->klist_devices, NULL, NULL);
init_completion(&drv->unloaded);
return bus_add_driver(drv);
}
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index 2b8755db76c..940ce41f188 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -505,12 +505,36 @@ static int platform_match(struct device * dev, struct device_driver * drv)
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
-static int platform_suspend(struct device * dev, pm_message_t state)
+static int platform_suspend(struct device *dev, pm_message_t mesg)
{
int ret = 0;
if (dev->driver && dev->driver->suspend)
- ret = dev->driver->suspend(dev, state);
+ ret = dev->driver->suspend(dev, mesg);
+
+ return ret;
+}
+
+static int platform_suspend_late(struct device *dev, pm_message_t mesg)
+{
+ struct platform_driver *drv = to_platform_driver(dev->driver);
+ struct platform_device *pdev = container_of(dev, struct platform_device, dev);
+ int ret = 0;
+
+ if (dev->driver && drv->suspend_late)
+ ret = drv->suspend_late(pdev, mesg);
+
+ return ret;
+}
+
+static int platform_resume_early(struct device *dev)
+{
+ struct platform_driver *drv = to_platform_driver(dev->driver);
+ struct platform_device *pdev = container_of(dev, struct platform_device, dev);
+ int ret = 0;
+
+ if (dev->driver && drv->resume_early)
+ ret = drv->resume_early(pdev);
return ret;
}
@@ -531,6 +555,8 @@ struct bus_type platform_bus_type = {
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
+ .suspend_late = platform_suspend_late,
+ .resume_early = platform_resume_early,
.resume = platform_resume,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c
index 826093ef4c7..020be36705a 100644
--- a/drivers/base/power/resume.c
+++ b/drivers/base/power/resume.c
@@ -38,13 +38,35 @@ int resume_device(struct device * dev)
dev_dbg(dev,"resuming\n");
error = dev->bus->resume(dev);
}
+ if (dev->class && dev->class->resume) {
+ dev_dbg(dev,"class resume\n");
+ error = dev->class->resume(dev);
+ }
up(&dev->sem);
TRACE_RESUME(error);
return error;
}
+static int resume_device_early(struct device * dev)
+{
+ int error = 0;
+ TRACE_DEVICE(dev);
+ TRACE_RESUME(0);
+ if (dev->bus && dev->bus->resume_early) {
+ dev_dbg(dev,"EARLY resume\n");
+ error = dev->bus->resume_early(dev);
+ }
+ TRACE_RESUME(error);
+ return error;
+}
+
+/*
+ * Resume the devices that have either not gone through
+ * the late suspend, or that did go through it but also
+ * went through the early resume
+ */
void dpm_resume(void)
{
down(&dpm_list_sem);
@@ -74,6 +96,7 @@ void dpm_resume(void)
void device_resume(void)
{
+ might_sleep();
down(&dpm_sem);
dpm_resume();
up(&dpm_sem);
@@ -83,12 +106,12 @@ EXPORT_SYMBOL_GPL(device_resume);
/**
- * device_power_up_irq - Power on some devices.
+ * dpm_power_up - Power on some devices.
*
* Walk the dpm_off_irq list and power each device up. This
* is used for devices that required they be powered down with
- * interrupts disabled. As devices are powered on, they are moved to
- * the dpm_suspended list.
+ * interrupts disabled. As devices are powered on, they are moved
+ * to the dpm_active list.
*
* Interrupts must be disabled when calling this.
*/
@@ -99,16 +122,14 @@ void dpm_power_up(void)
struct list_head * entry = dpm_off_irq.next;
struct device * dev = to_device(entry);
- get_device(dev);
- list_move_tail(entry, &dpm_active);
- resume_device(dev);
- put_device(dev);
+ list_move_tail(entry, &dpm_off);
+ resume_device_early(dev);
}
}
/**
- * device_pm_power_up - Turn on all devices that need special attention.
+ * device_power_up - Turn on all devices that need special attention.
*
* Power on system devices then devices that required we shut them down
* with interrupts disabled.
diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c
index 69509e02f70..ece136bf97e 100644
--- a/drivers/base/power/suspend.c
+++ b/drivers/base/power/suspend.c
@@ -34,6 +34,7 @@ static inline char *suspend_verb(u32 event)
switch (event) {
case PM_EVENT_SUSPEND: return "suspend";
case PM_EVENT_FREEZE: return "freeze";
+ case PM_EVENT_PRETHAW: return "prethaw";
default: return "(unknown suspend event)";
}
}
@@ -65,7 +66,19 @@ int suspend_device(struct device * dev, pm_message_t state)
dev->power.prev_state = dev->power.power_state;
- if (dev->bus && dev->bus->suspend && !dev->power.power_state.event) {
+ if (dev->class && dev->class->suspend && !dev->power.power_state.event) {
+ dev_dbg(dev, "class %s%s\n",
+ suspend_verb(state.event),
+ ((state.event == PM_EVENT_SUSPEND)
+ && device_may_wakeup(dev))
+ ? ", may wakeup"
+ : ""
+ );
+ error = dev->class->suspend(dev, state);
+ suspend_report_result(dev->class->suspend, error);
+ }
+
+ if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) {
dev_dbg(dev, "%s%s\n",
suspend_verb(state.event),
((state.event == PM_EVENT_SUSPEND)
@@ -81,15 +94,42 @@ int suspend_device(struct device * dev, pm_message_t state)
}
+/*
+ * This is called with interrupts off, only a single CPU
+ * running. We can't do down() on a semaphore (and we don't
+ * need the protection)
+ */
+static int suspend_device_late(struct device *dev, pm_message_t state)
+{
+ int error = 0;
+
+ if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) {
+ dev_dbg(dev, "LATE %s%s\n",
+ suspend_verb(state.event),
+ ((state.event == PM_EVENT_SUSPEND)
+ && device_may_wakeup(dev))
+ ? ", may wakeup"
+ : ""
+ );
+ error = dev->bus->suspend_late(dev, state);
+ suspend_report_result(dev->bus->suspend_late, error);
+ }
+ return error;
+}
+
/**
* device_suspend - Save state and stop all devices in system.
* @state: Power state to put each device in.
*
* Walk the dpm_active list, call ->suspend() for each device, and move
- * it to dpm_off.
- * Check the return value for each. If it returns 0, then we move the
- * the device to the dpm_off list. If it returns -EAGAIN, we move it to
- * the dpm_off_irq list. If we get a different error, try and back out.
+ * it to the dpm_off list.
+ *
+ * (For historical reasons, if it returns -EAGAIN, that used to mean
+ * that the device would be called again with interrupts disabled.
+ * These days, we use the "suspend_late()" callback for that, so we
+ * print a warning and consider it an error).
+ *
+ * If we get a different error, try and back out.
*
* If we hit a failure with any of the devices, call device_resume()
* above to bring the suspended devices back to life.
@@ -100,6 +140,7 @@ int device_suspend(pm_message_t state)
{
int error = 0;
+ might_sleep();
down(&dpm_sem);
down(&dpm_list_sem);
while (!list_empty(&dpm_active) && error == 0) {
@@ -115,39 +156,27 @@ int device_suspend(pm_message_t state)
/* Check if the device got removed */
if (!list_empty(&dev->power.entry)) {
- /* Move it to the dpm_off or dpm_off_irq list */
+ /* Move it to the dpm_off list */
if (!error)
list_move(&dev->power.entry, &dpm_off);
- else if (error == -EAGAIN) {
- list_move(&dev->power.entry, &dpm_off_irq);
- error = 0;
- }
}
if (error)
printk(KERN_ERR "Could not suspend device %s: "
- "error %d\n", kobject_name(&dev->kobj), error);
+ "error %d%s\n",
+ kobject_name(&dev->kobj), error,
+ error == -EAGAIN ? " (please convert to suspend_late)" : "");
put_device(dev);
}
up(&dpm_list_sem);
- if (error) {
- /* we failed... before resuming, bring back devices from
- * dpm_off_irq list back to main dpm_off list, we do want
- * to call resume() on them, in case they partially suspended
- * despite returning -EAGAIN
- */
- while (!list_empty(&dpm_off_irq)) {
- struct list_head * entry = dpm_off_irq.next;
- list_move(entry, &dpm_off);
- }
+ if (error)
dpm_resume();
- }
+
up(&dpm_sem);
return error;
}
EXPORT_SYMBOL_GPL(device_suspend);
-
/**
* device_power_down - Shut down special devices.
* @state: Power state to enter.
@@ -162,14 +191,17 @@ int device_power_down(pm_message_t state)
int error = 0;
struct device * dev;
- list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) {
- if ((error = suspend_device(dev, state)))
- break;
+ while (!list_empty(&dpm_off)) {
+ struct list_head * entry = dpm_off.prev;
+
+ dev = to_device(entry);
+ error = suspend_device_late(dev, state);
+ if (error)
+ goto Error;
+ list_move(&dev->power.entry, &dpm_off_irq);
}
- if (error)
- goto Error;
- if ((error = sysdev_suspend(state)))
- goto Error;
+
+ error = sysdev_suspend(state);
Done:
return error;
Error:
diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c
index 40d7242a07c..2d47517dbe3 100644
--- a/drivers/base/power/sysfs.c
+++ b/drivers/base/power/sysfs.c
@@ -7,22 +7,29 @@
#include "power.h"
+#ifdef CONFIG_PM_SYSFS_DEPRECATED
+
/**
* state - Control current power state of device
*
* show() returns the current power state of the device. '0' indicates
- * the device is on. Other values (1-3) indicate the device is in a low
+ * the device is on. Other values (2) indicate the device is in some low
* power state.
*
- * store() sets the current power state, which is an integer value
- * between 0-3. If the device is on ('0'), and the value written is
- * greater than 0, then the device is placed directly into the low-power
- * state (via its driver's ->suspend() method).
- * If the device is currently in a low-power state, and the value is 0,
- * the device is powered back on (via the ->resume() method).
- * If the device is in a low-power state, and a different low-power state
- * is requested, the device is first resumed, then suspended into the new
- * low-power state.
+ * store() sets the current power state, which is an integer valued
+ * 0, 2, or 3. Devices with bus.suspend_late(), or bus.resume_early()
+ * methods fail this operation; those methods couldn't be called.
+ * Otherwise,
+ *
+ * - If the recorded dev->power.power_state.event matches the
+ * target value, nothing is done.
+ * - If the recorded event code is nonzero, the device is reactivated
+ * by calling bus.resume() and/or class.resume().
+ * - If the target value is nonzero, the device is suspended by
+ * calling class.suspend() and/or bus.suspend() with event code
+ * PM_EVENT_SUSPEND.
+ *
+ * This mechanism is DEPRECATED and should only be used for testing.
*/
static ssize_t state_show(struct device * dev, struct device_attribute *attr, char * buf)
@@ -38,6 +45,10 @@ static ssize_t state_store(struct device * dev, struct device_attribute *attr, c
pm_message_t state;
int error = -EINVAL;
+ /* disallow incomplete suspend sequences */
+ if (dev->bus && (dev->bus->suspend_late || dev->bus->resume_early))
+ return error;
+
state.event = PM_EVENT_SUSPEND;
/* Older apps expected to write "3" here - confused with PCI D3 */
if ((n == 1) && !strcmp(buf, "3"))
@@ -57,6 +68,8 @@ static ssize_t state_store(struct device * dev, struct device_attribute *attr, c
static DEVICE_ATTR(state, 0644, state_show, state_store);
+#endif /* CONFIG_PM_SYSFS_DEPRECATED */
+
/*
* wakeup - Report/change current wakeup option for device
*
@@ -130,7 +143,9 @@ static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store);
static struct attribute * power_attrs[] = {
+#ifdef CONFIG_PM_SYSFS_DEPRECATED
&dev_attr_state.attr,
+#endif
&dev_attr_wakeup.attr,
NULL,
};