|
@@ -14,16 +14,15 @@
|
|
|
#include <linux/suspend.h>
|
|
|
#include <linux/seq_file.h>
|
|
|
#include <linux/debugfs.h>
|
|
|
+#include <trace/events/power.h>
|
|
|
|
|
|
#include "power.h"
|
|
|
|
|
|
-#define TIMEOUT 100
|
|
|
-
|
|
|
/*
|
|
|
* If set, the suspend/hibernate code will abort transitions to a sleep state
|
|
|
* if wakeup events are registered during or immediately before the transition.
|
|
|
*/
|
|
|
-bool events_check_enabled;
|
|
|
+bool events_check_enabled __read_mostly;
|
|
|
|
|
|
/*
|
|
|
* Combined counters of registered wakeup events and wakeup events in progress.
|
|
@@ -52,6 +51,8 @@ static void pm_wakeup_timer_fn(unsigned long data);
|
|
|
|
|
|
static LIST_HEAD(wakeup_sources);
|
|
|
|
|
|
+static DECLARE_WAIT_QUEUE_HEAD(wakeup_count_wait_queue);
|
|
|
+
|
|
|
/**
|
|
|
* wakeup_source_prepare - Prepare a new wakeup source for initialization.
|
|
|
* @ws: Wakeup source to prepare.
|
|
@@ -132,6 +133,7 @@ void wakeup_source_add(struct wakeup_source *ws)
|
|
|
spin_lock_init(&ws->lock);
|
|
|
setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws);
|
|
|
ws->active = false;
|
|
|
+ ws->last_time = ktime_get();
|
|
|
|
|
|
spin_lock_irq(&events_lock);
|
|
|
list_add_rcu(&ws->entry, &wakeup_sources);
|
|
@@ -374,12 +376,33 @@ EXPORT_SYMBOL_GPL(device_set_wakeup_enable);
|
|
|
*/
|
|
|
static void wakeup_source_activate(struct wakeup_source *ws)
|
|
|
{
|
|
|
+ unsigned int cec;
|
|
|
+
|
|
|
ws->active = true;
|
|
|
ws->active_count++;
|
|
|
ws->last_time = ktime_get();
|
|
|
+ if (ws->autosleep_enabled)
|
|
|
+ ws->start_prevent_time = ws->last_time;
|
|
|
|
|
|
/* Increment the counter of events in progress. */
|
|
|
- atomic_inc(&combined_event_count);
|
|
|
+ cec = atomic_inc_return(&combined_event_count);
|
|
|
+
|
|
|
+ trace_wakeup_source_activate(ws->name, cec);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * wakeup_source_report_event - Report wakeup event using the given source.
|
|
|
+ * @ws: Wakeup source to report the event for.
|
|
|
+ */
|
|
|
+static void wakeup_source_report_event(struct wakeup_source *ws)
|
|
|
+{
|
|
|
+ ws->event_count++;
|
|
|
+ /* This is racy, but the counter is approximate anyway. */
|
|
|
+ if (events_check_enabled)
|
|
|
+ ws->wakeup_count++;
|
|
|
+
|
|
|
+ if (!ws->active)
|
|
|
+ wakeup_source_activate(ws);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -397,10 +420,7 @@ void __pm_stay_awake(struct wakeup_source *ws)
|
|
|
|
|
|
spin_lock_irqsave(&ws->lock, flags);
|
|
|
|
|
|
- ws->event_count++;
|
|
|
- if (!ws->active)
|
|
|
- wakeup_source_activate(ws);
|
|
|
-
|
|
|
+ wakeup_source_report_event(ws);
|
|
|
del_timer(&ws->timer);
|
|
|
ws->timer_expires = 0;
|
|
|
|
|
@@ -432,6 +452,17 @@ void pm_stay_awake(struct device *dev)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(pm_stay_awake);
|
|
|
|
|
|
+#ifdef CONFIG_PM_AUTOSLEEP
|
|
|
+static void update_prevent_sleep_time(struct wakeup_source *ws, ktime_t now)
|
|
|
+{
|
|
|
+ ktime_t delta = ktime_sub(now, ws->start_prevent_time);
|
|
|
+ ws->prevent_sleep_time = ktime_add(ws->prevent_sleep_time, delta);
|
|
|
+}
|
|
|
+#else
|
|
|
+static inline void update_prevent_sleep_time(struct wakeup_source *ws,
|
|
|
+ ktime_t now) {}
|
|
|
+#endif
|
|
|
+
|
|
|
/**
|
|
|
* wakup_source_deactivate - Mark given wakeup source as inactive.
|
|
|
* @ws: Wakeup source to handle.
|
|
@@ -442,6 +473,7 @@ EXPORT_SYMBOL_GPL(pm_stay_awake);
|
|
|
*/
|
|
|
static void wakeup_source_deactivate(struct wakeup_source *ws)
|
|
|
{
|
|
|
+ unsigned int cnt, inpr, cec;
|
|
|
ktime_t duration;
|
|
|
ktime_t now;
|
|
|
|
|
@@ -468,14 +500,23 @@ static void wakeup_source_deactivate(struct wakeup_source *ws)
|
|
|
if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
|
|
|
ws->max_time = duration;
|
|
|
|
|
|
+ ws->last_time = now;
|
|
|
del_timer(&ws->timer);
|
|
|
ws->timer_expires = 0;
|
|
|
|
|
|
+ if (ws->autosleep_enabled)
|
|
|
+ update_prevent_sleep_time(ws, now);
|
|
|
+
|
|
|
/*
|
|
|
* Increment the counter of registered wakeup events and decrement the
|
|
|
* couter of wakeup events in progress simultaneously.
|
|
|
*/
|
|
|
- atomic_add(MAX_IN_PROGRESS, &combined_event_count);
|
|
|
+ cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
|
|
|
+ trace_wakeup_source_deactivate(ws->name, cec);
|
|
|
+
|
|
|
+ split_counters(&cnt, &inpr);
|
|
|
+ if (!inpr && waitqueue_active(&wakeup_count_wait_queue))
|
|
|
+ wake_up(&wakeup_count_wait_queue);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -536,8 +577,10 @@ static void pm_wakeup_timer_fn(unsigned long data)
|
|
|
spin_lock_irqsave(&ws->lock, flags);
|
|
|
|
|
|
if (ws->active && ws->timer_expires
|
|
|
- && time_after_eq(jiffies, ws->timer_expires))
|
|
|
+ && time_after_eq(jiffies, ws->timer_expires)) {
|
|
|
wakeup_source_deactivate(ws);
|
|
|
+ ws->expire_count++;
|
|
|
+ }
|
|
|
|
|
|
spin_unlock_irqrestore(&ws->lock, flags);
|
|
|
}
|
|
@@ -564,9 +607,7 @@ void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec)
|
|
|
|
|
|
spin_lock_irqsave(&ws->lock, flags);
|
|
|
|
|
|
- ws->event_count++;
|
|
|
- if (!ws->active)
|
|
|
- wakeup_source_activate(ws);
|
|
|
+ wakeup_source_report_event(ws);
|
|
|
|
|
|
if (!msec) {
|
|
|
wakeup_source_deactivate(ws);
|
|
@@ -608,24 +649,6 @@ void pm_wakeup_event(struct device *dev, unsigned int msec)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(pm_wakeup_event);
|
|
|
|
|
|
-/**
|
|
|
- * pm_wakeup_update_hit_counts - Update hit counts of all active wakeup sources.
|
|
|
- */
|
|
|
-static void pm_wakeup_update_hit_counts(void)
|
|
|
-{
|
|
|
- unsigned long flags;
|
|
|
- struct wakeup_source *ws;
|
|
|
-
|
|
|
- rcu_read_lock();
|
|
|
- list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
|
|
|
- spin_lock_irqsave(&ws->lock, flags);
|
|
|
- if (ws->active)
|
|
|
- ws->hit_count++;
|
|
|
- spin_unlock_irqrestore(&ws->lock, flags);
|
|
|
- }
|
|
|
- rcu_read_unlock();
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* pm_wakeup_pending - Check if power transition in progress should be aborted.
|
|
|
*
|
|
@@ -648,32 +671,38 @@ bool pm_wakeup_pending(void)
|
|
|
events_check_enabled = !ret;
|
|
|
}
|
|
|
spin_unlock_irqrestore(&events_lock, flags);
|
|
|
- if (ret)
|
|
|
- pm_wakeup_update_hit_counts();
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* pm_get_wakeup_count - Read the number of registered wakeup events.
|
|
|
* @count: Address to store the value at.
|
|
|
+ * @block: Whether or not to block.
|
|
|
*
|
|
|
- * Store the number of registered wakeup events at the address in @count. Block
|
|
|
- * if the current number of wakeup events being processed is nonzero.
|
|
|
+ * Store the number of registered wakeup events at the address in @count. If
|
|
|
+ * @block is set, block until the current number of wakeup events being
|
|
|
+ * processed is zero.
|
|
|
*
|
|
|
- * Return 'false' if the wait for the number of wakeup events being processed to
|
|
|
- * drop down to zero has been interrupted by a signal (and the current number
|
|
|
- * of wakeup events being processed is still nonzero). Otherwise return 'true'.
|
|
|
+ * Return 'false' if the current number of wakeup events being processed is
|
|
|
+ * nonzero. Otherwise return 'true'.
|
|
|
*/
|
|
|
-bool pm_get_wakeup_count(unsigned int *count)
|
|
|
+bool pm_get_wakeup_count(unsigned int *count, bool block)
|
|
|
{
|
|
|
unsigned int cnt, inpr;
|
|
|
|
|
|
- for (;;) {
|
|
|
- split_counters(&cnt, &inpr);
|
|
|
- if (inpr == 0 || signal_pending(current))
|
|
|
- break;
|
|
|
- pm_wakeup_update_hit_counts();
|
|
|
- schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT));
|
|
|
+ if (block) {
|
|
|
+ DEFINE_WAIT(wait);
|
|
|
+
|
|
|
+ for (;;) {
|
|
|
+ prepare_to_wait(&wakeup_count_wait_queue, &wait,
|
|
|
+ TASK_INTERRUPTIBLE);
|
|
|
+ split_counters(&cnt, &inpr);
|
|
|
+ if (inpr == 0 || signal_pending(current))
|
|
|
+ break;
|
|
|
+
|
|
|
+ schedule();
|
|
|
+ }
|
|
|
+ finish_wait(&wakeup_count_wait_queue, &wait);
|
|
|
}
|
|
|
|
|
|
split_counters(&cnt, &inpr);
|
|
@@ -703,11 +732,37 @@ bool pm_save_wakeup_count(unsigned int count)
|
|
|
events_check_enabled = true;
|
|
|
}
|
|
|
spin_unlock_irq(&events_lock);
|
|
|
- if (!events_check_enabled)
|
|
|
- pm_wakeup_update_hit_counts();
|
|
|
return events_check_enabled;
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_PM_AUTOSLEEP
|
|
|
+/**
|
|
|
+ * pm_wakep_autosleep_enabled - Modify autosleep_enabled for all wakeup sources.
|
|
|
+ * @enabled: Whether to set or to clear the autosleep_enabled flags.
|
|
|
+ */
|
|
|
+void pm_wakep_autosleep_enabled(bool set)
|
|
|
+{
|
|
|
+ struct wakeup_source *ws;
|
|
|
+ ktime_t now = ktime_get();
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+ list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
|
|
|
+ spin_lock_irq(&ws->lock);
|
|
|
+ if (ws->autosleep_enabled != set) {
|
|
|
+ ws->autosleep_enabled = set;
|
|
|
+ if (ws->active) {
|
|
|
+ if (set)
|
|
|
+ ws->start_prevent_time = now;
|
|
|
+ else
|
|
|
+ update_prevent_sleep_time(ws, now);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ spin_unlock_irq(&ws->lock);
|
|
|
+ }
|
|
|
+ rcu_read_unlock();
|
|
|
+}
|
|
|
+#endif /* CONFIG_PM_AUTOSLEEP */
|
|
|
+
|
|
|
static struct dentry *wakeup_sources_stats_dentry;
|
|
|
|
|
|
/**
|
|
@@ -723,27 +778,37 @@ static int print_wakeup_source_stats(struct seq_file *m,
|
|
|
ktime_t max_time;
|
|
|
unsigned long active_count;
|
|
|
ktime_t active_time;
|
|
|
+ ktime_t prevent_sleep_time;
|
|
|
int ret;
|
|
|
|
|
|
spin_lock_irqsave(&ws->lock, flags);
|
|
|
|
|
|
total_time = ws->total_time;
|
|
|
max_time = ws->max_time;
|
|
|
+ prevent_sleep_time = ws->prevent_sleep_time;
|
|
|
active_count = ws->active_count;
|
|
|
if (ws->active) {
|
|
|
- active_time = ktime_sub(ktime_get(), ws->last_time);
|
|
|
+ ktime_t now = ktime_get();
|
|
|
+
|
|
|
+ active_time = ktime_sub(now, ws->last_time);
|
|
|
total_time = ktime_add(total_time, active_time);
|
|
|
if (active_time.tv64 > max_time.tv64)
|
|
|
max_time = active_time;
|
|
|
+
|
|
|
+ if (ws->autosleep_enabled)
|
|
|
+ prevent_sleep_time = ktime_add(prevent_sleep_time,
|
|
|
+ ktime_sub(now, ws->start_prevent_time));
|
|
|
} else {
|
|
|
active_time = ktime_set(0, 0);
|
|
|
}
|
|
|
|
|
|
- ret = seq_printf(m, "%-12s\t%lu\t\t%lu\t\t%lu\t\t"
|
|
|
- "%lld\t\t%lld\t\t%lld\t\t%lld\n",
|
|
|
- ws->name, active_count, ws->event_count, ws->hit_count,
|
|
|
+ ret = seq_printf(m, "%-12s\t%lu\t\t%lu\t\t%lu\t\t%lu\t\t"
|
|
|
+ "%lld\t\t%lld\t\t%lld\t\t%lld\t\t%lld\n",
|
|
|
+ ws->name, active_count, ws->event_count,
|
|
|
+ ws->wakeup_count, ws->expire_count,
|
|
|
ktime_to_ms(active_time), ktime_to_ms(total_time),
|
|
|
- ktime_to_ms(max_time), ktime_to_ms(ws->last_time));
|
|
|
+ ktime_to_ms(max_time), ktime_to_ms(ws->last_time),
|
|
|
+ ktime_to_ms(prevent_sleep_time));
|
|
|
|
|
|
spin_unlock_irqrestore(&ws->lock, flags);
|
|
|
|
|
@@ -758,8 +823,9 @@ static int wakeup_sources_stats_show(struct seq_file *m, void *unused)
|
|
|
{
|
|
|
struct wakeup_source *ws;
|
|
|
|
|
|
- seq_puts(m, "name\t\tactive_count\tevent_count\thit_count\t"
|
|
|
- "active_since\ttotal_time\tmax_time\tlast_change\n");
|
|
|
+ seq_puts(m, "name\t\tactive_count\tevent_count\twakeup_count\t"
|
|
|
+ "expire_count\tactive_since\ttotal_time\tmax_time\t"
|
|
|
+ "last_change\tprevent_suspend_time\n");
|
|
|
|
|
|
rcu_read_lock();
|
|
|
list_for_each_entry_rcu(ws, &wakeup_sources, entry)
|