diff options
Diffstat (limited to 'drivers/base/power/wakeup.c')
| -rw-r--r-- | drivers/base/power/wakeup.c | 174 | 
1 files changed, 120 insertions, 54 deletions
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 2a3e581b8dc..cbb463b3a75 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -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); @@ -609,24 +650,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.   *   * Compare the current number of registered wakeup events with its preserved @@ -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)  |