aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/base/Kconfig2
-rw-r--r--drivers/base/base.h62
-rw-r--r--drivers/base/bus.c15
-rw-r--r--drivers/base/class.c42
-rw-r--r--drivers/base/core.c65
-rw-r--r--drivers/base/node.c21
-rw-r--r--drivers/base/power/generic_ops.c6
-rw-r--r--drivers/base/power/main.c176
-rw-r--r--drivers/base/power/runtime.c56
-rw-r--r--drivers/base/power/wakeup.c20
10 files changed, 244 insertions, 221 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index fd96345bc35c..d57e8d0fb823 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -70,7 +70,7 @@ config PREVENT_FIRMWARE_BUILD
If unsure say Y here.
config FW_LOADER
- tristate "Userspace firmware loading support" if EMBEDDED
+ tristate "Userspace firmware loading support" if EXPERT
default y
---help---
This option is provided for the case where no in-kernel-tree modules
diff --git a/drivers/base/base.h b/drivers/base/base.h
index 2ca7f5b7b824..19f49e41ce5d 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -1,31 +1,46 @@
/**
- * struct bus_type_private - structure to hold the private to the driver core portions of the bus_type structure.
+ * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
*
- * @subsys - the struct kset that defines this bus. This is the main kobject
- * @drivers_kset - the list of drivers associated with this bus
- * @devices_kset - the list of devices associated with this bus
+ * @subsys - the struct kset that defines this subsystem
+ * @devices_kset - the list of devices associated
+ *
+ * @drivers_kset - the list of drivers associated
* @klist_devices - the klist to iterate over the @devices_kset
* @klist_drivers - the klist to iterate over the @drivers_kset
* @bus_notifier - the bus notifier list for anything that cares about things
- * on this bus.
+ * on this bus.
* @bus - pointer back to the struct bus_type that this structure is associated
- * with.
+ * with.
+ *
+ * @class_interfaces - list of class_interfaces associated
+ * @glue_dirs - "glue" directory to put in-between the parent device to
+ * avoid namespace conflicts
+ * @class_mutex - mutex to protect the children, devices, and interfaces lists.
+ * @class - pointer back to the struct class that this structure is associated
+ * with.
*
* This structure is the one that is the actual kobject allowing struct
- * bus_type to be statically allocated safely. Nothing outside of the driver
- * core should ever touch these fields.
+ * bus_type/class to be statically allocated safely. Nothing outside of the
+ * driver core should ever touch these fields.
*/
-struct bus_type_private {
+struct subsys_private {
struct kset subsys;
- struct kset *drivers_kset;
struct kset *devices_kset;
+
+ struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
+
+ struct list_head class_interfaces;
+ struct kset glue_dirs;
+ struct mutex class_mutex;
+ struct class *class;
};
+#define to_subsys_private(obj) container_of(obj, struct subsys_private, subsys.kobj)
struct driver_private {
struct kobject kobj;
@@ -36,33 +51,6 @@ struct driver_private {
};
#define to_driver(obj) container_of(obj, struct driver_private, kobj)
-
-/**
- * struct class_private - structure to hold the private to the driver core portions of the class structure.
- *
- * @class_subsys - the struct kset that defines this class. This is the main kobject
- * @class_devices - list of devices associated with this class
- * @class_interfaces - list of class_interfaces associated with this class
- * @class_dirs - "glue" directory for virtual devices associated with this class
- * @class_mutex - mutex to protect the children, devices, and interfaces lists.
- * @class - pointer back to the struct class that this structure is associated
- * with.
- *
- * This structure is the one that is the actual kobject allowing struct
- * class to be statically allocated safely. Nothing outside of the driver
- * core should ever touch these fields.
- */
-struct class_private {
- struct kset class_subsys;
- struct klist class_devices;
- struct list_head class_interfaces;
- struct kset class_dirs;
- struct mutex class_mutex;
- struct class *class;
-};
-#define to_class(obj) \
- container_of(obj, struct class_private, class_subsys.kobj)
-
/**
* struct device_private - structure to hold the private to the driver core portions of the device structure.
*
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index 33c270a64db7..000e7b2006f8 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -20,7 +20,6 @@
#include "power/power.h"
#define to_bus_attr(_attr) container_of(_attr, struct bus_attribute, attr)
-#define to_bus(obj) container_of(obj, struct bus_type_private, subsys.kobj)
/*
* sysfs bindings for drivers
@@ -96,11 +95,11 @@ static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
- struct bus_type_private *bus_priv = to_bus(kobj);
+ struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->show)
- ret = bus_attr->show(bus_priv->bus, buf);
+ ret = bus_attr->show(subsys_priv->bus, buf);
return ret;
}
@@ -108,11 +107,11 @@ static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
- struct bus_type_private *bus_priv = to_bus(kobj);
+ struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->store)
- ret = bus_attr->store(bus_priv->bus, buf, count);
+ ret = bus_attr->store(subsys_priv->bus, buf, count);
return ret;
}
@@ -858,9 +857,9 @@ static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);
int bus_register(struct bus_type *bus)
{
int retval;
- struct bus_type_private *priv;
+ struct subsys_private *priv;
- priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
+ priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -976,7 +975,7 @@ struct klist *bus_get_device_klist(struct bus_type *bus)
EXPORT_SYMBOL_GPL(bus_get_device_klist);
/*
- * Yes, this forcably breaks the klist abstraction temporarily. It
+ * Yes, this forcibly breaks the klist abstraction temporarily. It
* just wants to sort the klist, not change reference counts and
* take/drop locks rapidly in the process. It does all this while
* holding the lock for the list, so objects can't otherwise be
diff --git a/drivers/base/class.c b/drivers/base/class.c
index 9c63a5687d69..4f1df2e8fd74 100644
--- a/drivers/base/class.c
+++ b/drivers/base/class.c
@@ -27,7 +27,7 @@ static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct class_attribute *class_attr = to_class_attr(attr);
- struct class_private *cp = to_class(kobj);
+ struct subsys_private *cp = to_subsys_private(kobj);
ssize_t ret = -EIO;
if (class_attr->show)
@@ -39,7 +39,7 @@ static ssize_t class_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct class_attribute *class_attr = to_class_attr(attr);
- struct class_private *cp = to_class(kobj);
+ struct subsys_private *cp = to_subsys_private(kobj);
ssize_t ret = -EIO;
if (class_attr->store)
@@ -49,7 +49,7 @@ static ssize_t class_attr_store(struct kobject *kobj, struct attribute *attr,
static void class_release(struct kobject *kobj)
{
- struct class_private *cp = to_class(kobj);
+ struct subsys_private *cp = to_subsys_private(kobj);
struct class *class = cp->class;
pr_debug("class '%s': release.\n", class->name);
@@ -65,7 +65,7 @@ static void class_release(struct kobject *kobj)
static const struct kobj_ns_type_operations *class_child_ns_type(struct kobject *kobj)
{
- struct class_private *cp = to_class(kobj);
+ struct subsys_private *cp = to_subsys_private(kobj);
struct class *class = cp->class;
return class->ns_type;
@@ -82,7 +82,7 @@ static struct kobj_type class_ktype = {
.child_ns_type = class_child_ns_type,
};
-/* Hotplug events for classes go to the class class_subsys */
+/* Hotplug events for classes go to the class subsys */
static struct kset *class_kset;
@@ -90,7 +90,7 @@ int class_create_file(struct class *cls, const struct class_attribute *attr)
{
int error;
if (cls)
- error = sysfs_create_file(&cls->p->class_subsys.kobj,
+ error = sysfs_create_file(&cls->p->subsys.kobj,
&attr->attr);
else
error = -EINVAL;
@@ -100,20 +100,20 @@ int class_create_file(struct class *cls, const struct class_attribute *attr)
void class_remove_file(struct class *cls, const struct class_attribute *attr)
{
if (cls)
- sysfs_remove_file(&cls->p->class_subsys.kobj, &attr->attr);
+ sysfs_remove_file(&cls->p->subsys.kobj, &attr->attr);
}
static struct class *class_get(struct class *cls)
{
if (cls)
- kset_get(&cls->p->class_subsys);
+ kset_get(&cls->p->subsys);
return cls;
}
static void class_put(struct class *cls)
{
if (cls)
- kset_put(&cls->p->class_subsys);
+ kset_put(&cls->p->subsys);
}
static int add_class_attrs(struct class *cls)
@@ -162,7 +162,7 @@ static void klist_class_dev_put(struct klist_node *n)
int __class_register(struct class *cls, struct lock_class_key *key)
{
- struct class_private *cp;
+ struct subsys_private *cp;
int error;
pr_debug("device class '%s': registering\n", cls->name);
@@ -170,11 +170,11 @@ int __class_register(struct class *cls, struct lock_class_key *key)
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
- klist_init(&cp->class_devices, klist_class_dev_get, klist_class_dev_put);
+ klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
INIT_LIST_HEAD(&cp->class_interfaces);
- kset_init(&cp->class_dirs);
+ kset_init(&cp->glue_dirs);
__mutex_init(&cp->class_mutex, "struct class mutex", key);
- error = kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name);
+ error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
@@ -187,15 +187,15 @@ int __class_register(struct class *cls, struct lock_class_key *key)
#if defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */
if (!sysfs_deprecated || cls != &block_class)
- cp->class_subsys.kobj.kset = class_kset;
+ cp->subsys.kobj.kset = class_kset;
#else
- cp->class_subsys.kobj.kset = class_kset;
+ cp->subsys.kobj.kset = class_kset;
#endif
- cp->class_subsys.kobj.ktype = &class_ktype;
+ cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;
- error = kset_register(&cp->class_subsys);
+ error = kset_register(&cp->subsys);
if (error) {
kfree(cp);
return error;
@@ -210,7 +210,7 @@ void class_unregister(struct class *cls)
{
pr_debug("device class '%s': unregistering\n", cls->name);
remove_class_attrs(cls);
- kset_unregister(&cls->p->class_subsys);
+ kset_unregister(&cls->p->subsys);
}
static void class_create_release(struct class *cls)
@@ -295,7 +295,7 @@ void class_dev_iter_init(struct class_dev_iter *iter, struct class *class,
if (start)
start_knode = &start->knode_class;
- klist_iter_init_node(&class->p->class_devices, &iter->ki, start_knode);
+ klist_iter_init_node(&class->p->klist_devices, &iter->ki, start_knode);
iter->type = type;
}
EXPORT_SYMBOL_GPL(class_dev_iter_init);
@@ -482,8 +482,8 @@ void class_interface_unregister(struct class_interface *class_intf)
class_put(parent);
}
-ssize_t show_class_attr_string(struct class *class, struct class_attribute *attr,
- char *buf)
+ssize_t show_class_attr_string(struct class *class,
+ struct class_attribute *attr, char *buf)
{
struct class_attribute_string *cs;
cs = container_of(attr, struct class_attribute_string, attr);
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 6ed645411c40..080e9ca11017 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -338,6 +338,35 @@ static void device_remove_attributes(struct device *dev,
device_remove_file(dev, &attrs[i]);
}
+static int device_add_bin_attributes(struct device *dev,
+ struct bin_attribute *attrs)
+{
+ int error = 0;
+ int i;
+
+ if (attrs) {
+ for (i = 0; attr_name(attrs[i]); i++) {
+ error = device_create_bin_file(dev, &attrs[i]);
+ if (error)
+ break;
+ }
+ if (error)
+ while (--i >= 0)
+ device_remove_bin_file(dev, &attrs[i]);
+ }
+ return error;
+}
+
+static void device_remove_bin_attributes(struct device *dev,
+ struct bin_attribute *attrs)
+{
+ int i;
+
+ if (attrs)
+ for (i = 0; attr_name(attrs[i]); i++)
+ device_remove_bin_file(dev, &attrs[i]);
+}
+
static int device_add_groups(struct device *dev,
const struct attribute_group **groups)
{
@@ -378,12 +407,15 @@ static int device_add_attrs(struct device *dev)
error = device_add_attributes(dev, class->dev_attrs);
if (error)
return error;
+ error = device_add_bin_attributes(dev, class->dev_bin_attrs);
+ if (error)
+ goto err_remove_class_attrs;
}
if (type) {
error = device_add_groups(dev, type->groups);
if (error)
- goto err_remove_class_attrs;
+ goto err_remove_class_bin_attrs;
}
error = device_add_groups(dev, dev->groups);
@@ -395,6 +427,9 @@ static int device_add_attrs(struct device *dev)
err_remove_type_groups:
if (type)
device_remove_groups(dev, type->groups);
+ err_remove_class_bin_attrs:
+ if (class)
+ device_remove_bin_attributes(dev, class->dev_bin_attrs);
err_remove_class_attrs:
if (class)
device_remove_attributes(dev, class->dev_attrs);
@@ -412,8 +447,10 @@ static void device_remove_attrs(struct device *dev)
if (type)
device_remove_groups(dev, type->groups);
- if (class)
+ if (class) {
device_remove_attributes(dev, class->dev_attrs);
+ device_remove_bin_attributes(dev, class->dev_bin_attrs);
+ }
}
@@ -610,7 +647,7 @@ class_dir_create_and_add(struct class *class, struct kobject *parent_kobj)
dir->class = class;
kobject_init(&dir->kobj, &class_dir_ktype);
- dir->kobj.kset = &class->p->class_dirs;
+ dir->kobj.kset = &class->p->glue_dirs;
retval = kobject_add(&dir->kobj, parent_kobj, "%s", class->name);
if (retval < 0) {
@@ -635,7 +672,7 @@ static struct kobject *get_device_parent(struct device *dev,
if (sysfs_deprecated && dev->class == &block_class) {
if (parent && parent->class == &block_class)
return &parent->kobj;
- return &block_class.p->class_subsys.kobj;
+ return &block_class.p->subsys.kobj;
}
#endif
@@ -654,13 +691,13 @@ static struct kobject *get_device_parent(struct device *dev,
mutex_lock(&gdp_mutex);
/* find our class-directory at the parent and reference it */
- spin_lock(&dev->class->p->class_dirs.list_lock);
- list_for_each_entry(k, &dev->class->p->class_dirs.list, entry)
+ spin_lock(&dev->class->p->glue_dirs.list_lock);
+ list_for_each_entry(k, &dev->class->p->glue_dirs.list, entry)
if (k->parent == parent_kobj) {
kobj = kobject_get(k);
break;
}
- spin_unlock(&dev->class->p->class_dirs.list_lock);
+ spin_unlock(&dev->class->p->glue_dirs.list_lock);
if (kobj) {
mutex_unlock(&gdp_mutex);
return kobj;
@@ -682,7 +719,7 @@ static void cleanup_glue_dir(struct device *dev, struct kobject *glue_dir)
{
/* see if we live in a "glue" directory */
if (!glue_dir || !dev->class ||
- glue_dir->kset != &dev->class->p->class_dirs)
+ glue_dir->kset != &dev->class->p->glue_dirs)
return;
kobject_put(glue_dir);
@@ -709,7 +746,7 @@ static int device_add_class_symlinks(struct device *dev)
return 0;
error = sysfs_create_link(&dev->kobj,
- &dev->class->p->class_subsys.kobj,
+ &dev->class->p->subsys.kobj,
"subsystem");
if (error)
goto out;
@@ -728,7 +765,7 @@ static int device_add_class_symlinks(struct device *dev)
#endif
/* link in the class directory pointing to the device */
- error = sysfs_create_link(&dev->class->p->class_subsys.kobj,
+ error = sysfs_create_link(&dev->class->p->subsys.kobj,
&dev->kobj, dev_name(dev));
if (error)
goto out_device;
@@ -756,7 +793,7 @@ static void device_remove_class_symlinks(struct device *dev)
if (sysfs_deprecated && dev->class == &block_class)
return;
#endif
- sysfs_delete_link(&dev->class->p->class_subsys.kobj, &dev->kobj, dev_name(dev));
+ sysfs_delete_link(&dev->class->p->subsys.kobj, &dev->kobj, dev_name(dev));
}
/**
@@ -947,7 +984,7 @@ int device_add(struct device *dev)
mutex_lock(&dev->class->p->class_mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
- &dev->class->p->class_devices);
+ &dev->class->p->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
@@ -1513,6 +1550,8 @@ EXPORT_SYMBOL_GPL(device_destroy);
* exclusion between two different calls of device_rename
* on the same device to ensure that new_name is valid and
* won't conflict with other devices.
+ *
+ * "Never use this function, bad things will happen" - gregkh
*/
int device_rename(struct device *dev, const char *new_name)
{
@@ -1535,7 +1574,7 @@ int device_rename(struct device *dev, const char *new_name)
}
if (dev->class) {
- error = sysfs_rename_link(&dev->class->p->class_subsys.kobj,
+ error = sysfs_rename_link(&dev->class->p->subsys.kobj,
&dev->kobj, old_device_name, new_name);
if (error)
goto out;
diff --git a/drivers/base/node.c b/drivers/base/node.c
index ce012a9c6201..36b43052001d 100644
--- a/drivers/base/node.c
+++ b/drivers/base/node.c
@@ -117,12 +117,21 @@ static ssize_t node_read_meminfo(struct sys_device * dev,
"Node %d WritebackTmp: %8lu kB\n"
"Node %d Slab: %8lu kB\n"
"Node %d SReclaimable: %8lu kB\n"
- "Node %d SUnreclaim: %8lu kB\n",
+ "Node %d SUnreclaim: %8lu kB\n"
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+ "Node %d AnonHugePages: %8lu kB\n"
+#endif
+ ,
nid, K(node_page_state(nid, NR_FILE_DIRTY)),
nid, K(node_page_state(nid, NR_WRITEBACK)),
nid, K(node_page_state(nid, NR_FILE_PAGES)),
nid, K(node_page_state(nid, NR_FILE_MAPPED)),
- nid, K(node_page_state(nid, NR_ANON_PAGES)),
+ nid, K(node_page_state(nid, NR_ANON_PAGES)
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+ + node_page_state(nid, NR_ANON_TRANSPARENT_HUGEPAGES) *
+ HPAGE_PMD_NR
+#endif
+ ),
nid, K(node_page_state(nid, NR_SHMEM)),
nid, node_page_state(nid, NR_KERNEL_STACK) *
THREAD_SIZE / 1024,
@@ -133,7 +142,13 @@ static ssize_t node_read_meminfo(struct sys_device * dev,
nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE) +
node_page_state(nid, NR_SLAB_UNRECLAIMABLE)),
nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE)),
- nid, K(node_page_state(nid, NR_SLAB_UNRECLAIMABLE)));
+ nid, K(node_page_state(nid, NR_SLAB_UNRECLAIMABLE))
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+ , nid,
+ K(node_page_state(nid, NR_ANON_TRANSPARENT_HUGEPAGES) *
+ HPAGE_PMD_NR)
+#endif
+ );
n += hugetlb_report_node_meminfo(nid, buf + n);
return n;
}
diff --git a/drivers/base/power/generic_ops.c b/drivers/base/power/generic_ops.c
index 81f2c84697f4..42f97f925629 100644
--- a/drivers/base/power/generic_ops.c
+++ b/drivers/base/power/generic_ops.c
@@ -39,7 +39,7 @@ EXPORT_SYMBOL_GPL(pm_generic_runtime_idle);
*
* If PM operations are defined for the @dev's driver and they include
* ->runtime_suspend(), execute it and return its error code. Otherwise,
- * return -EINVAL.
+ * return 0.
*/
int pm_generic_runtime_suspend(struct device *dev)
{
@@ -58,7 +58,7 @@ EXPORT_SYMBOL_GPL(pm_generic_runtime_suspend);
*
* If PM operations are defined for the @dev's driver and they include
* ->runtime_resume(), execute it and return its error code. Otherwise,
- * return -EINVAL.
+ * return 0.
*/
int pm_generic_runtime_resume(struct device *dev)
{
@@ -185,7 +185,7 @@ static int __pm_generic_resume(struct device *dev, int event)
return 0;
ret = callback(dev);
- if (!ret) {
+ if (!ret && pm_runtime_enabled(dev)) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index ead3e79d6fcf..83404973f97a 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -8,7 +8,7 @@
*
*
* The driver model core calls device_pm_add() when a device is registered.
- * This will intialize the embedded device_pm_info object in the device
+ * This will initialize the embedded device_pm_info object in the device
* and add it to the list of power-controlled devices. sysfs entries for
* controlling device power management will also be added.
*
@@ -26,6 +26,7 @@
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/async.h>
+#include <linux/suspend.h>
#include "../base.h"
#include "power.h"
@@ -41,16 +42,13 @@
*/
LIST_HEAD(dpm_list);
+LIST_HEAD(dpm_prepared_list);
+LIST_HEAD(dpm_suspended_list);
+LIST_HEAD(dpm_noirq_list);
static DEFINE_MUTEX(dpm_list_mtx);
static pm_message_t pm_transition;
-/*
- * Set once the preparation of devices for a PM transition has started, reset
- * before starting to resume devices. Protected by dpm_list_mtx.
- */
-static bool transition_started;
-
static int async_error;
/**
@@ -59,7 +57,7 @@ static int async_error;
*/
void device_pm_init(struct device *dev)
{
- dev->power.status = DPM_ON;
+ dev->power.in_suspend = false;
init_completion(&dev->power.completion);
complete_all(&dev->power.completion);
dev->power.wakeup = NULL;
@@ -90,22 +88,11 @@ void device_pm_unlock(void)
void device_pm_add(struct device *dev)
{
pr_debug("PM: Adding info for %s:%s\n",
- dev->bus ? dev->bus->name : "No Bus",
- kobject_name(&dev->kobj));
+ dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
mutex_lock(&dpm_list_mtx);
- if (dev->parent) {
- if (dev->parent->power.status >= DPM_SUSPENDING)
- dev_warn(dev, "parent %s should not be sleeping\n",
- dev_name(dev->parent));
- } else if (transition_started) {
- /*
- * We refuse to register parentless devices while a PM
- * transition is in progress in order to avoid leaving them
- * unhandled down the road
- */
- dev_WARN(dev, "Parentless device registered during a PM transaction\n");
- }
-
+ if (dev->parent && dev->parent->power.in_suspend)
+ dev_warn(dev, "parent %s should not be sleeping\n",
+ dev_name(dev->parent));
list_add_tail(&dev->power.entry, &dpm_list);
mutex_unlock(&dpm_list_mtx);
}
@@ -117,8 +104,7 @@ void device_pm_add(struct device *dev)
void device_pm_remove(struct device *dev)
{
pr_debug("PM: Removing info for %s:%s\n",
- dev->bus ? dev->bus->name : "No Bus",
- kobject_name(&dev->kobj));
+ dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
complete_all(&dev->power.completion);
mutex_lock(&dpm_list_mtx);
list_del_init(&dev->power.entry);
@@ -135,10 +121,8 @@ void device_pm_remove(struct device *dev)
void device_pm_move_before(struct device *deva, struct device *devb)
{
pr_debug("PM: Moving %s:%s before %s:%s\n",
- deva->bus ? deva->bus->name : "No Bus",
- kobject_name(&deva->kobj),
- devb->bus ? devb->bus->name : "No Bus",
- kobject_name(&devb->kobj));
+ deva->bus ? deva->bus->name : "No Bus", dev_name(deva),
+ devb->bus ? devb->bus->name : "No Bus", dev_name(devb));
/* Delete deva from dpm_list and reinsert before devb. */
list_move_tail(&deva->power.entry, &devb->power.entry);
}
@@ -151,10 +135,8 @@ void device_pm_move_before(struct device *deva, struct device *devb)
void device_pm_move_after(struct device *deva, struct device *devb)
{
pr_debug("PM: Moving %s:%s after %s:%s\n",
- deva->bus ? deva->bus->name : "No Bus",
- kobject_name(&deva->kobj),
- devb->bus ? devb->bus->name : "No Bus",
- kobject_name(&devb->kobj));
+ deva->bus ? deva->bus->name : "No Bus", dev_name(deva),
+ devb->bus ? devb->bus->name : "No Bus", dev_name(devb));
/* Delete deva from dpm_list and reinsert after devb. */
list_move(&deva->power.entry, &devb->power.entry);
}
@@ -166,8 +148,7 @@ void device_pm_move_after(struct device *deva, struct device *devb)
void device_pm_move_last(struct device *dev)
{
pr_debug("PM: Moving %s:%s to end of list\n",
- dev->bus ? dev->bus->name : "No Bus",
- kobject_name(&dev->kobj));
+ dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
list_move_tail(&dev->power.entry, &dpm_list);
}
@@ -303,7 +284,7 @@ static int pm_noirq_op(struct device *dev,
pm_message_t state)
{
int error = 0;
- ktime_t calltime, delta, rettime;
+ ktime_t calltime = ktime_set(0, 0), delta, rettime;
if (initcall_debug) {
pr_info("calling %s+ @ %i, parent: %s\n",
@@ -405,7 +386,7 @@ static void pm_dev_err(struct device *dev, pm_message_t state, char *info,
int error)
{
printk(KERN_ERR "PM: Device %s failed to %s%s: error %d\n",
- kobject_name(&dev->kobj), pm_verb(state.event), info, error);
+ dev_name(dev), pm_verb(state.event), info, error);
}
static void dpm_show_time(ktime_t starttime, pm_message_t state, char *info)
@@ -475,33 +456,24 @@ End:
*/
void dpm_resume_noirq(pm_message_t state)
{
- struct list_head list;
ktime_t starttime = ktime_get();
- INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
- transition_started = false;
- while (!list_empty(&dpm_list)) {
- struct device *dev = to_device(dpm_list.next);
+ while (!list_empty(&dpm_noirq_list)) {
+ struct device *dev = to_device(dpm_noirq_list.next);
+ int error;
get_device(dev);
- if (dev->power.status > DPM_OFF) {
- int error;
-
- dev->power.status = DPM_OFF;
- mutex_unlock(&dpm_list_mtx);
+ list_move_tail(&dev->power.entry, &dpm_suspended_list);
+ mutex_unlock(&dpm_list_mtx);
- error = device_resume_noirq(dev, state);
+ error = device_resume_noirq(dev, state);
+ if (error)
+ pm_dev_err(dev, state, " early", error);
- mutex_lock(&dpm_list_mtx);
- if (error)
- pm_dev_err(dev, state, " early", error);
- }
- if (!list_empty(&dev->power.entry))
- list_move_tail(&dev->power.entry, &list);
+ mutex_lock(&dpm_list_mtx);
put_device(dev);
}
- list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
dpm_show_time(starttime, state, "early");
resume_device_irqs();
@@ -544,7 +516,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
dpm_wait(dev->parent, async);
device_lock(dev);
- dev->power.status = DPM_RESUMING;
+ dev->power.in_suspend = false;
if (dev->bus) {
if (dev->bus->pm) {
@@ -610,19 +582,14 @@ static bool is_async(struct device *dev)
*/
static void dpm_resume(pm_message_t state)
{
- struct list_head list;
struct device *dev;
ktime_t starttime = ktime_get();
- INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
- list_for_each_entry(dev, &dpm_list, power.entry) {
- if (dev->power.status < DPM_OFF)
- continue;
-
+ list_for_each_entry(dev, &dpm_suspended_list, power.entry) {
INIT_COMPLETION(dev->power.completion);
if (is_async(dev)) {
get_device(dev);
@@ -630,28 +597,24 @@ static void dpm_resume(pm_message_t state)
}
}
- while (!list_empty(&dpm_list)) {
- dev = to_device(dpm_list.next);
+ while (!list_empty(&dpm_suspended_list)) {
+ dev = to_device(dpm_suspended_list.next);
get_device(dev);
- if (dev->power.status >= DPM_OFF && !is_async(dev)) {
+ if (!is_async(dev)) {
int error;
mutex_unlock(&dpm_list_mtx);
error = device_resume(dev, state, false);
-
- mutex_lock(&dpm_list_mtx);
if (error)
pm_dev_err(dev, state, "", error);
- } else if (dev->power.status == DPM_SUSPENDING) {
- /* Allow new children of the device to be registered */
- dev->power.status = DPM_RESUMING;
+
+ mutex_lock(&dpm_list_mtx);
}
if (!list_empty(&dev->power.entry))
- list_move_tail(&dev->power.entry, &list);
+ list_move_tail(&dev->power.entry, &dpm_prepared_list);
put_device(dev);
}
- list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
dpm_show_time(starttime, state, NULL);
@@ -697,22 +660,18 @@ static void dpm_complete(pm_message_t state)
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
- transition_started = false;
- while (!list_empty(&dpm_list)) {
- struct device *dev = to_device(dpm_list.prev);
+ while (!list_empty(&dpm_prepared_list)) {
+ struct device *dev = to_device(dpm_prepared_list.prev);
get_device(dev);
- if (dev->power.status > DPM_ON) {
- dev->power.status = DPM_ON;
- mutex_unlock(&dpm_list_mtx);
+ dev->power.in_suspend = false;
+ list_move(&dev->power.entry, &list);
+ mutex_unlock(&dpm_list_mtx);
- device_complete(dev, state);
- pm_runtime_put_sync(dev);
+ device_complete(dev, state);
+ pm_runtime_put_sync(dev);
- mutex_lock(&dpm_list_mtx);
- }
- if (!list_empty(&dev->power.entry))
- list_move(&dev->power.entry, &list);
+ mutex_lock(&dpm_list_mtx);
put_device(dev);
}
list_splice(&list, &dpm_list);
@@ -802,15 +761,13 @@ End:
*/
int dpm_suspend_noirq(pm_message_t state)
{
- struct list_head list;
ktime_t starttime = ktime_get();
int error = 0;
- INIT_LIST_HEAD(&list);
suspend_device_irqs();
mutex_lock(&dpm_list_mtx);
- while (!list_empty(&dpm_list)) {
- struct device *dev = to_device(dpm_list.prev);
+ while (!list_empty(&dpm_suspended_list)) {
+ struct device *dev = to_device(dpm_suspended_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
@@ -823,12 +780,10 @@ int dpm_suspend_noirq(pm_message_t state)
put_device(dev);
break;
}
- dev->power.status = DPM_OFF_IRQ;
if (!list_empty(&dev->power.entry))
- list_move(&dev->power.entry, &list);
+ list_move(&dev->power.entry, &dpm_noirq_list);
put_device(dev);
}
- list_splice_tail(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
if (error)
dpm_resume_noirq(resume_event(state));
@@ -876,6 +831,11 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
if (async_error)
goto End;
+ if (pm_wakeup_pending()) {
+ async_error = -EBUSY;
+ goto End;
+ }
+
if (dev->class) {
if (dev->class->pm) {
pm_dev_dbg(dev, state, "class ");
@@ -907,9 +867,6 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
}
}
- if (!error)
- dev->power.status = DPM_OFF;
-
End:
device_unlock(dev);
complete_all(&dev->power.completion);
@@ -951,16 +908,14 @@ static int device_suspend(struct device *dev)
*/
static int dpm_suspend(pm_message_t state)
{
- struct list_head list;
ktime_t starttime = ktime_get();
int error = 0;
- INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
pm_transition = state;
async_error = 0;
- while (!list_empty(&dpm_list)) {
- struct device *dev = to_device(dpm_list.prev);
+ while (!list_empty(&dpm_prepared_list)) {
+ struct device *dev = to_device(dpm_prepared_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
@@ -974,12 +929,11 @@ static int dpm_suspend(pm_message_t state)
break;
}
if (!list_empty(&dev->power.entry))
- list_move(&dev->power.entry, &list);
+ list_move(&dev->power.entry, &dpm_suspended_list);
put_device(dev);
if (async_error)
break;
}
- list_splice(&list, dpm_list.prev);
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();
if (!error)
@@ -1038,22 +992,20 @@ static int device_prepare(struct device *dev, pm_message_t state)
*/
static int dpm_prepare(pm_message_t state)
{
- struct list_head list;
int error = 0;
- INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
- transition_started = true;
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.next);
get_device(dev);
- dev->power.status = DPM_PREPARING;
mutex_unlock(&dpm_list_mtx);
pm_runtime_get_noresume(dev);
- if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) {
- /* Wake-up requested during system sleep transition. */
+ if (pm_runtime_barrier(dev) && device_may_wakeup(dev))
+ pm_wakeup_event(dev, 0);
+
+ if (pm_wakeup_pending()) {
pm_runtime_put_sync(dev);
error = -EBUSY;
} else {
@@ -1062,24 +1014,22 @@ static int dpm_prepare(pm_message_t state)
mutex_lock(&dpm_list_mtx);
if (error) {
- dev->power.status = DPM_ON;
if (error == -EAGAIN) {
put_device(dev);
error = 0;
continue;
}
- printk(KERN_ERR "PM: Failed to prepare device %s "
- "for power transition: error %d\n",
- kobject_name(&dev->kobj), error);
+ printk(KERN_INFO "PM: Device %s not prepared "
+ "for power transition: code %d\n",
+ dev_name(dev), error);
put_device(dev);
break;
}
- dev->power.status = DPM_SUSPENDING;
+ dev->power.in_suspend = true;
if (!list_empty(&dev->power.entry))
- list_move_tail(&dev->power.entry, &list);
+ list_move_tail(&dev->power.entry, &dpm_prepared_list);
put_device(dev);
}
- list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
return error;
}
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index 02c652be83e7..42615b419dfb 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -250,13 +250,16 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev)
if (!cb)
return -ENOSYS;
- spin_unlock_irq(&dev->power.lock);
+ if (dev->power.irq_safe) {
+ retval = cb(dev);
+ } else {
+ spin_unlock_irq(&dev->power.lock);
- retval = cb(dev);
+ retval = cb(dev);
- spin_lock_irq(&dev->power.lock);
+ spin_lock_irq(&dev->power.lock);
+ }
dev->power.runtime_error = retval;
-
return retval;
}
@@ -404,12 +407,15 @@ static int rpm_suspend(struct device *dev, int rpmflags)
goto out;
}
- if (parent && !parent->power.ignore_children) {
- spin_unlock_irq(&dev->power.lock);
+ /* Maybe the parent is now able to suspend. */
+ if (parent && !parent->power.ignore_children && !dev->power.irq_safe) {
+ spin_unlock(&dev->power.lock);
- pm_request_idle(parent);
+ spin_lock(&parent->power.lock);
+ rpm_idle(parent, RPM_ASYNC);
+ spin_unlock(&parent->power.lock);
- spin_lock_irq(&dev->power.lock);
+ spin_lock(&dev->power.lock);
}
out:
@@ -527,10 +533,13 @@ static int rpm_resume(struct device *dev, int rpmflags)
if (!parent && dev->parent) {
/*
- * Increment the parent's resume counter and resume it if
- * necessary.
+ * Increment the parent's usage counter and resume it if
+ * necessary. Not needed if dev is irq-safe; then the
+ * parent is permanently resumed.
*/
parent = dev->parent;
+ if (dev->power.irq_safe)
+ goto skip_parent;
spin_unlock(&dev->power.lock);
pm_runtime_get_noresume(parent);
@@ -553,6 +562,7 @@ static int rpm_resume(struct device *dev, int rpmflags)
goto out;
goto repeat;
}
+ skip_parent:
if (dev->power.no_callbacks)
goto no_callback; /* Assume success. */
@@ -584,7 +594,7 @@ static int rpm_resume(struct device *dev, int rpmflags)
rpm_idle(dev, RPM_ASYNC);
out:
- if (parent) {
+ if (parent && !dev->power.irq_safe) {
spin_unlock_irq(&dev->power.lock);
pm_runtime_put(parent);
@@ -1065,7 +1075,6 @@ EXPORT_SYMBOL_GPL(pm_runtime_allow);
* Set the power.no_callbacks flag, which tells the PM core that this
* device is power-managed through its parent and has no run-time PM
* callbacks of its own. The run-time sysfs attributes will be removed.
- *
*/
void pm_runtime_no_callbacks(struct device *dev)
{
@@ -1078,6 +1087,27 @@ void pm_runtime_no_callbacks(struct device *dev)
EXPORT_SYMBOL_GPL(pm_runtime_no_callbacks);
/**
+ * pm_runtime_irq_safe - Leave interrupts disabled during callbacks.
+ * @dev: Device to handle
+ *
+ * Set the power.irq_safe flag, which tells the PM core that the
+ * ->runtime_suspend() and ->runtime_resume() callbacks for this device should
+ * always be invoked with the spinlock held and interrupts disabled. It also
+ * causes the parent's usage counter to be permanently incremented, preventing
+ * the parent from runtime suspending -- otherwise an irq-safe child might have
+ * to wait for a non-irq-safe parent.
+ */
+void pm_runtime_irq_safe(struct device *dev)
+{
+ if (dev->parent)
+ pm_runtime_get_sync(dev->parent);
+ spin_lock_irq(&dev->power.lock);
+ dev->power.irq_safe = 1;
+ spin_unlock_irq(&dev->power.lock);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_irq_safe);
+
+/**
* update_autosuspend - Handle a change to a device's autosuspend settings.
* @dev: Device to handle.
* @old_delay: The former autosuspend_delay value.
@@ -1199,4 +1229,6 @@ void pm_runtime_remove(struct device *dev)
/* Change the status back to 'suspended' to match the initial status. */
if (dev->power.runtime_status == RPM_ACTIVE)
pm_runtime_set_suspended(dev);
+ if (dev->power.irq_safe && dev->parent)
+ pm_runtime_put_sync(dev->parent);
}
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
index 71c5528e1c35..8ec406d8f548 100644
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -542,26 +542,26 @@ static void pm_wakeup_update_hit_counts(void)
}
/**
- * pm_check_wakeup_events - Check for new wakeup events.
+ * pm_wakeup_pending - Check if power transition in progress should be aborted.
*
* Compare the current number of registered wakeup events with its preserved
- * value from the past to check if new wakeup events have been registered since
- * the old value was stored. Check if the current number of wakeup events being
- * processed is zero.
+ * value from the past and return true if new wakeup events have been registered
+ * since the old value was stored. Also return true if the current number of
+ * wakeup events being processed is different from zero.
*/
-bool pm_check_wakeup_events(void)
+bool pm_wakeup_pending(void)
{
unsigned long flags;
- bool ret = true;
+ bool ret = false;
spin_lock_irqsave(&events_lock, flags);
if (events_check_enabled) {
- ret = ((unsigned int)atomic_read(&event_count) == saved_count)
- && !atomic_read(&events_in_progress);
- events_check_enabled = ret;
+ ret = ((unsigned int)atomic_read(&event_count) != saved_count)
+ || atomic_read(&events_in_progress);
+ events_check_enabled = !ret;
}
spin_unlock_irqrestore(&events_lock, flags);
- if (!ret)
+ if (ret)
pm_wakeup_update_hit_counts();
return ret;
}