Commit 074037ec authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

PM / Wakeup: Introduce wakeup source objects and event statistics (v3)

Introduce struct wakeup_source for representing system wakeup sources
within the kernel and for collecting statistics related to them.
Make the recently introduced helper functions pm_wakeup_event(),
pm_stay_awake() and pm_relax() use struct wakeup_source objects
internally, so that wakeup statistics associated with wakeup devices
can be collected and reported in a consistent way (the definition of
pm_relax() is changed, which is harmless, because this function is
not called directly by anyone yet).  Introduce new wakeup-related
sysfs device attributes in /sys/devices/.../power for reporting the
device wakeup statistics.

Change the global wakeup events counters event_count and
events_in_progress into atomic variables, so that it is not necessary
to acquire a global spinlock in pm_wakeup_event(), pm_stay_awake()
and pm_relax(), which should allow us to avoid lock contention in
these functions on SMP systems with many wakeup devices.
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Acked-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 0702d9ee
...@@ -77,3 +77,73 @@ Description: ...@@ -77,3 +77,73 @@ Description:
devices this attribute is set to "enabled" by bus type code or devices this attribute is set to "enabled" by bus type code or
device drivers and in that cases it should be safe to leave the device drivers and in that cases it should be safe to leave the
default value. default value.
What: /sys/devices/.../power/wakeup_count
Date: September 2010
Contact: Rafael J. Wysocki <rjw@sisk.pl>
Description:
The /sys/devices/.../wakeup_count attribute contains the number
of signaled wakeup events associated with the device. This
attribute is read-only. If the device is not enabled to wake up
the system from sleep states, this attribute is empty.
What: /sys/devices/.../power/wakeup_active_count
Date: September 2010
Contact: Rafael J. Wysocki <rjw@sisk.pl>
Description:
The /sys/devices/.../wakeup_active_count attribute contains the
number of times the processing of wakeup events associated with
the device was completed (at the kernel level). This attribute
is read-only. If the device is not enabled to wake up the
system from sleep states, this attribute is empty.
What: /sys/devices/.../power/wakeup_hit_count
Date: September 2010
Contact: Rafael J. Wysocki <rjw@sisk.pl>
Description:
The /sys/devices/.../wakeup_hit_count attribute contains the
number of times the processing of a wakeup event associated with
the device might prevent the system from entering a sleep state.
This attribute is read-only. If the device is not enabled to
wake up the system from sleep states, this attribute is empty.
What: /sys/devices/.../power/wakeup_active
Date: September 2010
Contact: Rafael J. Wysocki <rjw@sisk.pl>
Description:
The /sys/devices/.../wakeup_active attribute contains either 1,
or 0, depending on whether or not a wakeup event associated with
the device is being processed (1). This attribute is read-only.
If the device is not enabled to wake up the system from sleep
states, this attribute is empty.
What: /sys/devices/.../power/wakeup_total_time_ms
Date: September 2010
Contact: Rafael J. Wysocki <rjw@sisk.pl>
Description:
The /sys/devices/.../wakeup_total_time_ms attribute contains
the total time of processing wakeup events associated with the
device, in milliseconds. This attribute is read-only. If the
device is not enabled to wake up the system from sleep states,
this attribute is empty.
What: /sys/devices/.../power/wakeup_max_time_ms
Date: September 2010
Contact: Rafael J. Wysocki <rjw@sisk.pl>
Description:
The /sys/devices/.../wakeup_max_time_ms attribute contains
the maximum time of processing a single wakeup event associated
with the device, in milliseconds. This attribute is read-only.
If the device is not enabled to wake up the system from sleep
states, this attribute is empty.
What: /sys/devices/.../power/wakeup_last_time_ms
Date: September 2010
Contact: Rafael J. Wysocki <rjw@sisk.pl>
Description:
The /sys/devices/.../wakeup_last_time_ms attribute contains
the value of the monotonic clock corresponding to the time of
signaling the last wakeup event associated with the device, in
milliseconds. This attribute is read-only. If the device is
not enabled to wake up the system from sleep states, this
attribute is empty.
...@@ -60,7 +60,8 @@ void device_pm_init(struct device *dev) ...@@ -60,7 +60,8 @@ void device_pm_init(struct device *dev)
dev->power.status = DPM_ON; dev->power.status = DPM_ON;
init_completion(&dev->power.completion); init_completion(&dev->power.completion);
complete_all(&dev->power.completion); complete_all(&dev->power.completion);
dev->power.wakeup_count = 0; dev->power.wakeup = NULL;
spin_lock_init(&dev->power.lock);
pm_runtime_init(dev); pm_runtime_init(dev);
} }
...@@ -120,6 +121,7 @@ void device_pm_remove(struct device *dev) ...@@ -120,6 +121,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);
device_wakeup_disable(dev);
pm_runtime_remove(dev); pm_runtime_remove(dev);
} }
......
...@@ -34,6 +34,7 @@ extern void device_pm_move_last(struct device *); ...@@ -34,6 +34,7 @@ extern void device_pm_move_last(struct device *);
static inline void device_pm_init(struct device *dev) static inline void device_pm_init(struct device *dev)
{ {
spin_lock_init(&dev->power.lock);
pm_runtime_init(dev); pm_runtime_init(dev);
} }
......
...@@ -1099,8 +1099,6 @@ EXPORT_SYMBOL_GPL(pm_runtime_allow); ...@@ -1099,8 +1099,6 @@ EXPORT_SYMBOL_GPL(pm_runtime_allow);
*/ */
void pm_runtime_init(struct device *dev) void pm_runtime_init(struct device *dev)
{ {
spin_lock_init(&dev->power.lock);
dev->power.runtime_status = RPM_SUSPENDED; dev->power.runtime_status = RPM_SUSPENDED;
dev->power.idle_notification = false; dev->power.idle_notification = false;
......
...@@ -210,11 +210,122 @@ static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store); ...@@ -210,11 +210,122 @@ static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store);
static ssize_t wakeup_count_show(struct device *dev, static ssize_t wakeup_count_show(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
return sprintf(buf, "%lu\n", dev->power.wakeup_count); unsigned long count = 0;
bool enabled = false;
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
count = dev->power.wakeup->event_count;
enabled = true;
}
spin_unlock_irq(&dev->power.lock);
return enabled ? sprintf(buf, "%lu\n", count) : sprintf(buf, "\n");
} }
static DEVICE_ATTR(wakeup_count, 0444, wakeup_count_show, NULL); static DEVICE_ATTR(wakeup_count, 0444, wakeup_count_show, NULL);
#endif
static ssize_t wakeup_active_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned long count = 0;
bool enabled = false;
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
count = dev->power.wakeup->active_count;
enabled = true;
}
spin_unlock_irq(&dev->power.lock);
return enabled ? sprintf(buf, "%lu\n", count) : sprintf(buf, "\n");
}
static DEVICE_ATTR(wakeup_active_count, 0444, wakeup_active_count_show, NULL);
static ssize_t wakeup_hit_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned long count = 0;
bool enabled = false;
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
count = dev->power.wakeup->hit_count;
enabled = true;
}
spin_unlock_irq(&dev->power.lock);
return enabled ? sprintf(buf, "%lu\n", count) : sprintf(buf, "\n");
}
static DEVICE_ATTR(wakeup_hit_count, 0444, wakeup_hit_count_show, NULL);
static ssize_t wakeup_active_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned int active = 0;
bool enabled = false;
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
active = dev->power.wakeup->active;
enabled = true;
}
spin_unlock_irq(&dev->power.lock);
return enabled ? sprintf(buf, "%u\n", active) : sprintf(buf, "\n");
}
static DEVICE_ATTR(wakeup_active, 0444, wakeup_active_show, NULL);
static ssize_t wakeup_total_time_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
s64 msec = 0;
bool enabled = false;
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
msec = ktime_to_ms(dev->power.wakeup->total_time);
enabled = true;
}
spin_unlock_irq(&dev->power.lock);
return enabled ? sprintf(buf, "%lld\n", msec) : sprintf(buf, "\n");
}
static DEVICE_ATTR(wakeup_total_time_ms, 0444, wakeup_total_time_show, NULL);
static ssize_t wakeup_max_time_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
s64 msec = 0;
bool enabled = false;
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
msec = ktime_to_ms(dev->power.wakeup->max_time);
enabled = true;
}
spin_unlock_irq(&dev->power.lock);
return enabled ? sprintf(buf, "%lld\n", msec) : sprintf(buf, "\n");
}
static DEVICE_ATTR(wakeup_max_time_ms, 0444, wakeup_max_time_show, NULL);
static ssize_t wakeup_last_time_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
s64 msec = 0;
bool enabled = false;
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
msec = ktime_to_ms(dev->power.wakeup->last_time);
enabled = true;
}
spin_unlock_irq(&dev->power.lock);
return enabled ? sprintf(buf, "%lld\n", msec) : sprintf(buf, "\n");
}
static DEVICE_ATTR(wakeup_last_time_ms, 0444, wakeup_last_time_show, NULL);
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM_ADVANCED_DEBUG #ifdef CONFIG_PM_ADVANCED_DEBUG
#ifdef CONFIG_PM_RUNTIME #ifdef CONFIG_PM_RUNTIME
...@@ -288,6 +399,12 @@ static struct attribute * power_attrs[] = { ...@@ -288,6 +399,12 @@ static struct attribute * power_attrs[] = {
&dev_attr_wakeup.attr, &dev_attr_wakeup.attr,
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
&dev_attr_wakeup_count.attr, &dev_attr_wakeup_count.attr,
&dev_attr_wakeup_active_count.attr,
&dev_attr_wakeup_hit_count.attr,
&dev_attr_wakeup_active.attr,
&dev_attr_wakeup_total_time_ms.attr,
&dev_attr_wakeup_max_time_ms.attr,
&dev_attr_wakeup_last_time_ms.attr,
#endif #endif
#ifdef CONFIG_PM_ADVANCED_DEBUG #ifdef CONFIG_PM_ADVANCED_DEBUG
&dev_attr_async.attr, &dev_attr_async.attr,
......
This diff is collapsed.
...@@ -448,23 +448,24 @@ enum rpm_request { ...@@ -448,23 +448,24 @@ enum rpm_request {
RPM_REQ_RESUME, RPM_REQ_RESUME,
}; };
struct wakeup_source;
struct dev_pm_info { struct dev_pm_info {
pm_message_t power_state; pm_message_t power_state;
unsigned int can_wakeup:1; unsigned int can_wakeup:1;
unsigned int should_wakeup:1;
unsigned async_suspend:1; unsigned async_suspend:1;
enum dpm_state status; /* Owned by the PM core */ enum dpm_state status; /* Owned by the PM core */
spinlock_t lock;
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
struct list_head entry; struct list_head entry;
struct completion completion; struct completion completion;
unsigned long wakeup_count; struct wakeup_source *wakeup;
#endif #endif
#ifdef CONFIG_PM_RUNTIME #ifdef CONFIG_PM_RUNTIME
struct timer_list suspend_timer; struct timer_list suspend_timer;
unsigned long timer_expires; unsigned long timer_expires;
struct work_struct work; struct work_struct work;
wait_queue_head_t wait_queue; wait_queue_head_t wait_queue;
spinlock_t lock;
atomic_t usage_count; atomic_t usage_count;
atomic_t child_count; atomic_t child_count;
unsigned int disable_depth:3; unsigned int disable_depth:3;
...@@ -559,11 +560,6 @@ extern void __suspend_report_result(const char *function, void *fn, int ret); ...@@ -559,11 +560,6 @@ extern void __suspend_report_result(const char *function, void *fn, int ret);
} while (0) } while (0)
extern void device_pm_wait_for_dev(struct device *sub, struct device *dev); extern void device_pm_wait_for_dev(struct device *sub, struct device *dev);
/* drivers/base/power/wakeup.c */
extern void pm_wakeup_event(struct device *dev, unsigned int msec);
extern void pm_stay_awake(struct device *dev);
extern void pm_relax(void);
#else /* !CONFIG_PM_SLEEP */ #else /* !CONFIG_PM_SLEEP */
#define device_pm_lock() do {} while (0) #define device_pm_lock() do {} while (0)
...@@ -577,10 +573,6 @@ static inline int dpm_suspend_start(pm_message_t state) ...@@ -577,10 +573,6 @@ static inline int dpm_suspend_start(pm_message_t state)
#define suspend_report_result(fn, ret) do {} while (0) #define suspend_report_result(fn, ret) do {} while (0)
static inline void device_pm_wait_for_dev(struct device *a, struct device *b) {} static inline void device_pm_wait_for_dev(struct device *a, struct device *b) {}
static inline void pm_wakeup_event(struct device *dev, unsigned int msec) {}
static inline void pm_stay_awake(struct device *dev) {}
static inline void pm_relax(void) {}
#endif /* !CONFIG_PM_SLEEP */ #endif /* !CONFIG_PM_SLEEP */
/* How to reorder dpm_list after device_move() */ /* How to reorder dpm_list after device_move() */
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* pm_wakeup.h - Power management wakeup interface * pm_wakeup.h - Power management wakeup interface
* *
* Copyright (C) 2008 Alan Stern * Copyright (C) 2008 Alan Stern
* Copyright (C) 2010 Rafael J. Wysocki, Novell Inc.
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
...@@ -27,19 +28,77 @@ ...@@ -27,19 +28,77 @@
#include <linux/types.h> #include <linux/types.h>
#ifdef CONFIG_PM /**
* struct wakeup_source - Representation of wakeup sources
/* Changes to device_may_wakeup take effect on the next pm state change.
* *
* By default, most devices should leave wakeup disabled. The exceptions * @total_time: Total time this wakeup source has been active.
* are devices that everyone expects to be wakeup sources: keyboards, * @max_time: Maximum time this wakeup source has been continuously active.
* power buttons, possibly network interfaces, etc. * @last_time: Monotonic clock when the wakeup source's was activated last time.
* @event_count: Number of signaled wakeup events.
* @active_count: Number of times the wakeup sorce was activated.
* @relax_count: Number of times the wakeup sorce was deactivated.
* @hit_count: Number of times the wakeup sorce might abort system suspend.
* @active: Status of the wakeup source.
*/ */
static inline void device_init_wakeup(struct device *dev, bool val) struct wakeup_source {
char *name;
struct list_head entry;
spinlock_t lock;
struct timer_list timer;
unsigned long timer_expires;
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
unsigned long event_count;
unsigned long active_count;
unsigned long relax_count;
unsigned long hit_count;
unsigned int active:1;
};
#ifdef CONFIG_PM_SLEEP
/*
* Changes to device_may_wakeup take effect on the next pm state change.
*/
static inline void device_set_wakeup_capable(struct device *dev, bool capable)
{
dev->power.can_wakeup = capable;
}
static inline bool device_can_wakeup(struct device *dev)
{
return dev->power.can_wakeup;
}
static inline bool device_may_wakeup(struct device *dev)
{ {
dev->power.can_wakeup = dev->power.should_wakeup = val; return dev->power.can_wakeup && !!dev->power.wakeup;
} }
/* drivers/base/power/wakeup.c */
extern struct wakeup_source *wakeup_source_create(const char *name);
extern void wakeup_source_destroy(struct wakeup_source *ws);
extern void wakeup_source_add(struct wakeup_source *ws);
extern void wakeup_source_remove(struct wakeup_source *ws);
extern struct wakeup_source *wakeup_source_register(const char *name);
extern void wakeup_source_unregister(struct wakeup_source *ws);
extern int device_wakeup_enable(struct device *dev);
extern int device_wakeup_disable(struct device *dev);
extern int device_init_wakeup(struct device *dev, bool val);
extern int device_set_wakeup_enable(struct device *dev, bool enable);
extern void __pm_stay_awake(struct wakeup_source *ws);
extern void pm_stay_awake(struct device *dev);
extern void __pm_relax(struct wakeup_source *ws);
extern void pm_relax(struct device *dev);
extern void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec);
extern void pm_wakeup_event(struct device *dev, unsigned int msec);
#else /* !CONFIG_PM_SLEEP */
static inline void device_set_wakeup_capable(struct device *dev, bool capable) static inline void device_set_wakeup_capable(struct device *dev, bool capable)
{ {
dev->power.can_wakeup = capable; dev->power.can_wakeup = capable;
...@@ -50,43 +109,63 @@ static inline bool device_can_wakeup(struct device *dev) ...@@ -50,43 +109,63 @@ static inline bool device_can_wakeup(struct device *dev)
return dev->power.can_wakeup; return dev->power.can_wakeup;
} }
static inline void device_set_wakeup_enable(struct device *dev, bool enable) static inline bool device_may_wakeup(struct device *dev)
{ {
dev->power.should_wakeup = enable; return false;
} }
static inline bool device_may_wakeup(struct device *dev) static inline struct wakeup_source *wakeup_source_create(const char *name)
{ {
return dev->power.can_wakeup && dev->power.should_wakeup; return NULL;
} }
#else /* !CONFIG_PM */ static inline void wakeup_source_destroy(struct wakeup_source *ws) {}
static inline void wakeup_source_add(struct wakeup_source *ws) {}
/* For some reason the following routines work even without CONFIG_PM */ static inline void wakeup_source_remove(struct wakeup_source *ws) {}
static inline void device_init_wakeup(struct device *dev, bool val)
static inline struct wakeup_source *wakeup_source_register(const char *name)
{ {
dev->power.can_wakeup = val; return NULL;
} }
static inline void device_set_wakeup_capable(struct device *dev, bool capable) static inline void wakeup_source_unregister(struct wakeup_source *ws) {}
static inline int device_wakeup_enable(struct device *dev)
{ {
dev->power.can_wakeup = capable; return -EINVAL;
} }
static inline bool device_can_wakeup(struct device *dev) static inline int device_wakeup_disable(struct device *dev)
{ {
return dev->power.can_wakeup; return 0;
} }
static inline void device_set_wakeup_enable(struct device *dev, bool enable) static inline int device_init_wakeup(struct device *dev, bool val)
{ {
dev->power.can_wakeup = val;
return val ? -EINVAL : 0;
} }
static inline bool device_may_wakeup(struct device *dev)
static inline int device_set_wakeup_enable(struct device *dev, bool enable)
{ {
return false; return -EINVAL;
} }
#endif /* !CONFIG_PM */ static inline void __pm_stay_awake(struct wakeup_source *ws) {}
static inline void pm_stay_awake(struct device *dev) {}
static inline void __pm_relax(struct wakeup_source *ws) {}
static inline void pm_relax(struct device *dev) {}
static inline void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec) {}
static inline void pm_wakeup_event(struct device *dev, unsigned int msec) {}
#endif /* !CONFIG_PM_SLEEP */
#endif /* _LINUX_PM_WAKEUP_H */ #endif /* _LINUX_PM_WAKEUP_H */
...@@ -293,8 +293,8 @@ extern int unregister_pm_notifier(struct notifier_block *nb); ...@@ -293,8 +293,8 @@ extern int unregister_pm_notifier(struct notifier_block *nb);
extern bool events_check_enabled; extern bool events_check_enabled;
extern bool pm_check_wakeup_events(void); extern bool pm_check_wakeup_events(void);
extern bool pm_get_wakeup_count(unsigned long *count); extern bool pm_get_wakeup_count(unsigned int *count);
extern bool pm_save_wakeup_count(unsigned long count); extern bool pm_save_wakeup_count(unsigned int count);
#else /* !CONFIG_PM_SLEEP */ #else /* !CONFIG_PM_SLEEP */
static inline int register_pm_notifier(struct notifier_block *nb) static inline int register_pm_notifier(struct notifier_block *nb)
......
...@@ -237,18 +237,18 @@ static ssize_t wakeup_count_show(struct kobject *kobj, ...@@ -237,18 +237,18 @@ static ssize_t wakeup_count_show(struct kobject *kobj,
struct kobj_attribute *attr, struct kobj_attribute *attr,
char *buf) char *buf)
{ {
unsigned long val; unsigned int val;
return pm_get_wakeup_count(&val) ? sprintf(buf, "%lu\n", val) : -EINTR; return pm_get_wakeup_count(&val) ? sprintf(buf, "%u\n", val) : -EINTR;
} }
static ssize_t wakeup_count_store(struct kobject *kobj, static ssize_t wakeup_count_store(struct kobject *kobj,
struct kobj_attribute *attr, struct kobj_attribute *attr,
const char *buf, size_t n) const char *buf, size_t n)
{ {
unsigned long val; unsigned int val;
if (sscanf(buf, "%lu", &val) == 1) { if (sscanf(buf, "%u", &val) == 1) {
if (pm_save_wakeup_count(val)) if (pm_save_wakeup_count(val))
return n; return n;
} }
......
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