Commit 5e928f77 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

PM: Introduce core framework for run-time PM of I/O devices (rev. 17)

Introduce a core framework for run-time power management of I/O
devices.  Add device run-time PM fields to 'struct dev_pm_info'
and device run-time PM callbacks to 'struct dev_pm_ops'.  Introduce
a run-time PM workqueue and define some device run-time PM helper
functions at the core level.  Document all these things.

Special thanks to Alan Stern for his help with the design and
multiple detailed reviews of the pereceding versions of this patch
and to Magnus Damm for testing feedback.
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Acked-by: default avatarMagnus Damm <damm@igel.co.jp>
parent 8400146d
This diff is collapsed.
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/kthread.h> #include <linux/kthread.h>
#include <linux/wait.h> #include <linux/wait.h>
#include <linux/async.h> #include <linux/async.h>
#include <linux/pm_runtime.h>
#include "base.h" #include "base.h"
#include "power/power.h" #include "power/power.h"
...@@ -202,7 +203,10 @@ int driver_probe_device(struct device_driver *drv, struct device *dev) ...@@ -202,7 +203,10 @@ int driver_probe_device(struct device_driver *drv, struct device *dev)
pr_debug("bus: '%s': %s: matched device %s with driver %s\n", pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name); drv->bus->name, __func__, dev_name(dev), drv->name);
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv); ret = really_probe(dev, drv);
pm_runtime_put_sync(dev);
return ret; return ret;
} }
...@@ -245,7 +249,9 @@ int device_attach(struct device *dev) ...@@ -245,7 +249,9 @@ int device_attach(struct device *dev)
ret = 0; ret = 0;
} }
} else { } else {
pm_runtime_get_noresume(dev);
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
pm_runtime_put_sync(dev);
} }
up(&dev->sem); up(&dev->sem);
return ret; return ret;
...@@ -306,6 +312,9 @@ static void __device_release_driver(struct device *dev) ...@@ -306,6 +312,9 @@ static void __device_release_driver(struct device *dev)
drv = dev->driver; drv = dev->driver;
if (drv) { if (drv) {
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
driver_sysfs_remove(dev); driver_sysfs_remove(dev);
if (dev->bus) if (dev->bus)
...@@ -324,6 +333,8 @@ static void __device_release_driver(struct device *dev) ...@@ -324,6 +333,8 @@ static void __device_release_driver(struct device *dev)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier, blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_UNBOUND_DRIVER, BUS_NOTIFY_UNBOUND_DRIVER,
dev); dev);
pm_runtime_put_sync(dev);
} }
} }
......
obj-$(CONFIG_PM) += sysfs.o obj-$(CONFIG_PM) += sysfs.o
obj-$(CONFIG_PM_SLEEP) += main.o obj-$(CONFIG_PM_SLEEP) += main.o
obj-$(CONFIG_PM_RUNTIME) += runtime.o
obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o
ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <linux/kallsyms.h> #include <linux/kallsyms.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/pm.h> #include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/resume-trace.h> #include <linux/resume-trace.h>
#include <linux/rwsem.h> #include <linux/rwsem.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
...@@ -48,6 +49,16 @@ static DEFINE_MUTEX(dpm_list_mtx); ...@@ -48,6 +49,16 @@ static DEFINE_MUTEX(dpm_list_mtx);
*/ */
static bool transition_started; static bool transition_started;
/**
* device_pm_init - Initialize the PM-related part of a device object
* @dev: Device object being initialized.
*/
void device_pm_init(struct device *dev)
{
dev->power.status = DPM_ON;
pm_runtime_init(dev);
}
/** /**
* device_pm_lock - lock the list of active devices used by the PM core * device_pm_lock - lock the list of active devices used by the PM core
*/ */
...@@ -105,6 +116,7 @@ void device_pm_remove(struct device *dev) ...@@ -105,6 +116,7 @@ void device_pm_remove(struct device *dev)
mutex_lock(&dpm_list_mtx); mutex_lock(&dpm_list_mtx);
list_del_init(&dev->power.entry); list_del_init(&dev->power.entry);
mutex_unlock(&dpm_list_mtx); mutex_unlock(&dpm_list_mtx);
pm_runtime_remove(dev);
} }
/** /**
...@@ -512,6 +524,7 @@ static void dpm_complete(pm_message_t state) ...@@ -512,6 +524,7 @@ static void dpm_complete(pm_message_t state)
mutex_unlock(&dpm_list_mtx); mutex_unlock(&dpm_list_mtx);
device_complete(dev, state); device_complete(dev, state);
pm_runtime_put_noidle(dev);
mutex_lock(&dpm_list_mtx); mutex_lock(&dpm_list_mtx);
} }
...@@ -757,7 +770,14 @@ static int dpm_prepare(pm_message_t state) ...@@ -757,7 +770,14 @@ static int dpm_prepare(pm_message_t state)
dev->power.status = DPM_PREPARING; dev->power.status = DPM_PREPARING;
mutex_unlock(&dpm_list_mtx); 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. */
pm_runtime_put_noidle(dev);
error = -EBUSY;
} else {
error = device_prepare(dev, state); error = device_prepare(dev, state);
}
mutex_lock(&dpm_list_mtx); mutex_lock(&dpm_list_mtx);
if (error) { if (error) {
......
static inline void device_pm_init(struct device *dev) #ifdef CONFIG_PM_RUNTIME
{
dev->power.status = DPM_ON; extern void pm_runtime_init(struct device *dev);
} extern void pm_runtime_remove(struct device *dev);
#else /* !CONFIG_PM_RUNTIME */
static inline void pm_runtime_init(struct device *dev) {}
static inline void pm_runtime_remove(struct device *dev) {}
#endif /* !CONFIG_PM_RUNTIME */
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
...@@ -16,23 +23,33 @@ static inline struct device *to_device(struct list_head *entry) ...@@ -16,23 +23,33 @@ static inline struct device *to_device(struct list_head *entry)
return container_of(entry, struct device, power.entry); return container_of(entry, struct device, power.entry);
} }
extern void device_pm_init(struct device *dev);
extern void device_pm_add(struct device *); extern void device_pm_add(struct device *);
extern void device_pm_remove(struct device *); extern void device_pm_remove(struct device *);
extern void device_pm_move_before(struct device *, struct device *); extern void device_pm_move_before(struct device *, struct device *);
extern void device_pm_move_after(struct device *, struct device *); extern void device_pm_move_after(struct device *, struct device *);
extern void device_pm_move_last(struct device *); extern void device_pm_move_last(struct device *);
#else /* CONFIG_PM_SLEEP */ #else /* !CONFIG_PM_SLEEP */
static inline void device_pm_init(struct device *dev)
{
pm_runtime_init(dev);
}
static inline void device_pm_remove(struct device *dev)
{
pm_runtime_remove(dev);
}
static inline void device_pm_add(struct device *dev) {} static inline void device_pm_add(struct device *dev) {}
static inline void device_pm_remove(struct device *dev) {}
static inline void device_pm_move_before(struct device *deva, static inline void device_pm_move_before(struct device *deva,
struct device *devb) {} struct device *devb) {}
static inline void device_pm_move_after(struct device *deva, static inline void device_pm_move_after(struct device *deva,
struct device *devb) {} struct device *devb) {}
static inline void device_pm_move_last(struct device *dev) {} static inline void device_pm_move_last(struct device *dev) {}
#endif #endif /* !CONFIG_PM_SLEEP */
#ifdef CONFIG_PM #ifdef CONFIG_PM
......
This diff is collapsed.
...@@ -22,6 +22,10 @@ ...@@ -22,6 +22,10 @@
#define _LINUX_PM_H #define _LINUX_PM_H
#include <linux/list.h> #include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/timer.h>
/* /*
* Callbacks for platform drivers to implement. * Callbacks for platform drivers to implement.
...@@ -165,6 +169,28 @@ typedef struct pm_message { ...@@ -165,6 +169,28 @@ typedef struct pm_message {
* It is allowed to unregister devices while the above callbacks are being * It is allowed to unregister devices while the above callbacks are being
* executed. However, it is not allowed to unregister a device from within any * executed. However, it is not allowed to unregister a device from within any
* of its own callbacks. * of its own callbacks.
*
* There also are the following callbacks related to run-time power management
* of devices:
*
* @runtime_suspend: Prepare the device for a condition in which it won't be
* able to communicate with the CPU(s) and RAM due to power management.
* This need not mean that the device should be put into a low power state.
* For example, if the device is behind a link which is about to be turned
* off, the device may remain at full power. If the device does go to low
* power and if device_may_wakeup(dev) is true, remote wake-up (i.e., a
* hardware mechanism allowing the device to request a change of its power
* state, such as PCI PME) should be enabled for it.
*
* @runtime_resume: Put the device into the fully active state in response to a
* wake-up event generated by hardware or at the request of software. If
* necessary, put the device into the full power state and restore its
* registers, so that it is fully operational.
*
* @runtime_idle: Device appears to be inactive and it might be put into a low
* power state if all of the necessary conditions are satisfied. Check
* these conditions and handle the device as appropriate, possibly queueing
* a suspend request for it. The return value is ignored by the PM core.
*/ */
struct dev_pm_ops { struct dev_pm_ops {
...@@ -182,6 +208,9 @@ struct dev_pm_ops { ...@@ -182,6 +208,9 @@ struct dev_pm_ops {
int (*thaw_noirq)(struct device *dev); int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev); int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev); int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
}; };
/** /**
...@@ -315,14 +344,80 @@ enum dpm_state { ...@@ -315,14 +344,80 @@ enum dpm_state {
DPM_OFF_IRQ, DPM_OFF_IRQ,
}; };
/**
* Device run-time power management status.
*
* These status labels are used internally by the PM core to indicate the
* current status of a device with respect to the PM core operations. They do
* not reflect the actual power state of the device or its status as seen by the
* driver.
*
* RPM_ACTIVE Device is fully operational. Indicates that the device
* bus type's ->runtime_resume() callback has completed
* successfully.
*
* RPM_SUSPENDED Device bus type's ->runtime_suspend() callback has
* completed successfully. The device is regarded as
* suspended.
*
* RPM_RESUMING Device bus type's ->runtime_resume() callback is being
* executed.
*
* RPM_SUSPENDING Device bus type's ->runtime_suspend() callback is being
* executed.
*/
enum rpm_status {
RPM_ACTIVE = 0,
RPM_RESUMING,
RPM_SUSPENDED,
RPM_SUSPENDING,
};
/**
* Device run-time power management request types.
*
* RPM_REQ_NONE Do nothing.
*
* RPM_REQ_IDLE Run the device bus type's ->runtime_idle() callback
*
* RPM_REQ_SUSPEND Run the device bus type's ->runtime_suspend() callback
*
* RPM_REQ_RESUME Run the device bus type's ->runtime_resume() callback
*/
enum rpm_request {
RPM_REQ_NONE = 0,
RPM_REQ_IDLE,
RPM_REQ_SUSPEND,
RPM_REQ_RESUME,
};
struct dev_pm_info { struct dev_pm_info {
pm_message_t power_state; pm_message_t power_state;
unsigned can_wakeup:1; unsigned int can_wakeup:1;
unsigned should_wakeup:1; unsigned int should_wakeup:1;
enum dpm_state status; /* Owned by the PM core */ enum dpm_state status; /* Owned by the PM core */
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
struct list_head entry; struct list_head entry;
#endif #endif
#ifdef CONFIG_PM_RUNTIME
struct timer_list suspend_timer;
unsigned long timer_expires;
struct work_struct work;
wait_queue_head_t wait_queue;
spinlock_t lock;
atomic_t usage_count;
atomic_t child_count;
unsigned int disable_depth:3;
unsigned int ignore_children:1;
unsigned int idle_notification:1;
unsigned int request_pending:1;
unsigned int deferred_resume:1;
enum rpm_request request;
enum rpm_status runtime_status;
int runtime_error;
#endif
}; };
/* /*
......
/*
* pm_runtime.h - Device run-time power management helper functions.
*
* Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>
*
* This file is released under the GPLv2.
*/
#ifndef _LINUX_PM_RUNTIME_H
#define _LINUX_PM_RUNTIME_H
#include <linux/device.h>
#include <linux/pm.h>
#ifdef CONFIG_PM_RUNTIME
extern struct workqueue_struct *pm_wq;
extern int pm_runtime_idle(struct device *dev);
extern int pm_runtime_suspend(struct device *dev);
extern int pm_runtime_resume(struct device *dev);
extern int pm_request_idle(struct device *dev);
extern int pm_schedule_suspend(struct device *dev, unsigned int delay);
extern int pm_request_resume(struct device *dev);
extern int __pm_runtime_get(struct device *dev, bool sync);
extern int __pm_runtime_put(struct device *dev, bool sync);
extern int __pm_runtime_set_status(struct device *dev, unsigned int status);
extern int pm_runtime_barrier(struct device *dev);
extern void pm_runtime_enable(struct device *dev);
extern void __pm_runtime_disable(struct device *dev, bool check_resume);
static inline bool pm_children_suspended(struct device *dev)
{
return dev->power.ignore_children
|| !atomic_read(&dev->power.child_count);
}
static inline void pm_suspend_ignore_children(struct device *dev, bool enable)
{
dev->power.ignore_children = enable;
}
static inline void pm_runtime_get_noresume(struct device *dev)
{
atomic_inc(&dev->power.usage_count);
}
static inline void pm_runtime_put_noidle(struct device *dev)
{
atomic_add_unless(&dev->power.usage_count, -1, 0);
}
#else /* !CONFIG_PM_RUNTIME */
static inline int pm_runtime_idle(struct device *dev) { return -ENOSYS; }
static inline int pm_runtime_suspend(struct device *dev) { return -ENOSYS; }
static inline int pm_runtime_resume(struct device *dev) { return 0; }
static inline int pm_request_idle(struct device *dev) { return -ENOSYS; }
static inline int pm_schedule_suspend(struct device *dev, unsigned int delay)
{
return -ENOSYS;
}
static inline int pm_request_resume(struct device *dev) { return 0; }
static inline int __pm_runtime_get(struct device *dev, bool sync) { return 1; }
static inline int __pm_runtime_put(struct device *dev, bool sync) { return 0; }
static inline int __pm_runtime_set_status(struct device *dev,
unsigned int status) { return 0; }
static inline int pm_runtime_barrier(struct device *dev) { return 0; }
static inline void pm_runtime_enable(struct device *dev) {}
static inline void __pm_runtime_disable(struct device *dev, bool c) {}
static inline bool pm_children_suspended(struct device *dev) { return false; }
static inline void pm_suspend_ignore_children(struct device *dev, bool en) {}
static inline void pm_runtime_get_noresume(struct device *dev) {}
static inline void pm_runtime_put_noidle(struct device *dev) {}
#endif /* !CONFIG_PM_RUNTIME */
static inline int pm_runtime_get(struct device *dev)
{
return __pm_runtime_get(dev, false);
}
static inline int pm_runtime_get_sync(struct device *dev)
{
return __pm_runtime_get(dev, true);
}
static inline int pm_runtime_put(struct device *dev)
{
return __pm_runtime_put(dev, false);
}
static inline int pm_runtime_put_sync(struct device *dev)
{
return __pm_runtime_put(dev, true);
}
static inline int pm_runtime_set_active(struct device *dev)
{
return __pm_runtime_set_status(dev, RPM_ACTIVE);
}
static inline void pm_runtime_set_suspended(struct device *dev)
{
__pm_runtime_set_status(dev, RPM_SUSPENDED);
}
static inline void pm_runtime_disable(struct device *dev)
{
__pm_runtime_disable(dev, true);
}
#endif
...@@ -208,3 +208,17 @@ config APM_EMULATION ...@@ -208,3 +208,17 @@ config APM_EMULATION
random kernel OOPSes or reboots that don't seem to be related to random kernel OOPSes or reboots that don't seem to be related to
anything, try disabling/enabling this option (or disabling/enabling anything, try disabling/enabling this option (or disabling/enabling
APM in your BIOS). APM in your BIOS).
config PM_RUNTIME
bool "Run-time PM core functionality"
depends on PM
---help---
Enable functionality allowing I/O devices to be put into energy-saving
(low power) states at run time (or autosuspended) after a specified
period of inactivity and woken up in response to a hardware-generated
wake-up event or a driver's request.
Hardware support is generally required for this functionality to work
and the bus type drivers of the buses the devices are on are
responsible for the actual handling of the autosuspend requests and
wake-up events.
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <linux/kobject.h> #include <linux/kobject.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/resume-trace.h> #include <linux/resume-trace.h>
#include <linux/workqueue.h>
#include "power.h" #include "power.h"
...@@ -217,8 +218,24 @@ static struct attribute_group attr_group = { ...@@ -217,8 +218,24 @@ static struct attribute_group attr_group = {
.attrs = g, .attrs = g,
}; };
#ifdef CONFIG_PM_RUNTIME
struct workqueue_struct *pm_wq;
static int __init pm_start_workqueue(void)
{
pm_wq = create_freezeable_workqueue("pm");
return pm_wq ? 0 : -ENOMEM;
}
#else
static inline int pm_start_workqueue(void) { return 0; }
#endif
static int __init pm_init(void) static int __init pm_init(void)
{ {
int error = pm_start_workqueue();
if (error)
return error;
power_kobj = kobject_create_and_add("power", NULL); power_kobj = kobject_create_and_add("power", NULL);
if (!power_kobj) if (!power_kobj)
return -ENOMEM; return -ENOMEM;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment