diff options
Diffstat (limited to 'drivers/base/power/wakeup.c')
| -rw-r--r-- | drivers/base/power/wakeup.c | 407 | 
1 files changed, 305 insertions, 102 deletions
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 71c5528e1c3..eb1bd2ecad8 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -10,26 +10,40 @@  #include <linux/slab.h>  #include <linux/sched.h>  #include <linux/capability.h> +#include <linux/export.h>  #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. + * They need to be modified together atomically, so it's better to use one + * atomic variable to hold them both. + */ +static atomic_t combined_event_count = ATOMIC_INIT(0); + +#define IN_PROGRESS_BITS	(sizeof(int) * 4) +#define MAX_IN_PROGRESS		((1 << IN_PROGRESS_BITS) - 1) + +static void split_counters(unsigned int *cnt, unsigned int *inpr) +{ +	unsigned int comb = atomic_read(&combined_event_count); + +	*cnt = (comb >> IN_PROGRESS_BITS); +	*inpr = comb & MAX_IN_PROGRESS; +} -/* The counter of registered wakeup events. */ -static atomic_t event_count = ATOMIC_INIT(0); -/* A preserved old value of event_count. */ +/* A preserved old value of the events counter. */  static unsigned int saved_count; -/* The counter of wakeup events being processed. */ -static atomic_t events_in_progress = ATOMIC_INIT(0);  static DEFINE_SPINLOCK(events_lock); @@ -37,6 +51,25 @@ 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. + * @name: Pointer to the name of the new wakeup source. + * + * Callers must ensure that the @name string won't be freed when @ws is still in + * use. + */ +void wakeup_source_prepare(struct wakeup_source *ws, const char *name) +{ +	if (ws) { +		memset(ws, 0, sizeof(*ws)); +		ws->name = name; +	} +} +EXPORT_SYMBOL_GPL(wakeup_source_prepare); +  /**   * wakeup_source_create - Create a struct wakeup_source object.   * @name: Name of the new wakeup source. @@ -45,37 +78,44 @@ struct wakeup_source *wakeup_source_create(const char *name)  {  	struct wakeup_source *ws; -	ws = kzalloc(sizeof(*ws), GFP_KERNEL); +	ws = kmalloc(sizeof(*ws), GFP_KERNEL);  	if (!ws)  		return NULL; -	spin_lock_init(&ws->lock); -	if (name) -		ws->name = kstrdup(name, GFP_KERNEL); - +	wakeup_source_prepare(ws, name ? kstrdup(name, GFP_KERNEL) : NULL);  	return ws;  }  EXPORT_SYMBOL_GPL(wakeup_source_create);  /** + * wakeup_source_drop - Prepare a struct wakeup_source object for destruction. + * @ws: Wakeup source to prepare for destruction. + * + * Callers must ensure that __pm_stay_awake() or __pm_wakeup_event() will never + * be run in parallel with this function for the same wakeup source object. + */ +void wakeup_source_drop(struct wakeup_source *ws) +{ +	if (!ws) +		return; + +	del_timer_sync(&ws->timer); +	__pm_relax(ws); +} +EXPORT_SYMBOL_GPL(wakeup_source_drop); + +/**   * wakeup_source_destroy - Destroy a struct wakeup_source object.   * @ws: Wakeup source to destroy. + * + * Use only for wakeup source objects created with wakeup_source_create().   */  void wakeup_source_destroy(struct wakeup_source *ws)  {  	if (!ws)  		return; -	spin_lock_irq(&ws->lock); -	while (ws->active) { -		spin_unlock_irq(&ws->lock); - -		schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT)); - -		spin_lock_irq(&ws->lock); -	} -	spin_unlock_irq(&ws->lock); - +	wakeup_source_drop(ws);  	kfree(ws->name);  	kfree(ws);  } @@ -87,16 +127,19 @@ EXPORT_SYMBOL_GPL(wakeup_source_destroy);   */  void wakeup_source_add(struct wakeup_source *ws)  { +	unsigned long flags; +  	if (WARN_ON(!ws))  		return; +	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); +	spin_lock_irqsave(&events_lock, flags);  	list_add_rcu(&ws->entry, &wakeup_sources); -	spin_unlock_irq(&events_lock); -	synchronize_rcu(); +	spin_unlock_irqrestore(&events_lock, flags);  }  EXPORT_SYMBOL_GPL(wakeup_source_add); @@ -106,12 +149,14 @@ EXPORT_SYMBOL_GPL(wakeup_source_add);   */  void wakeup_source_remove(struct wakeup_source *ws)  { +	unsigned long flags; +  	if (WARN_ON(!ws))  		return; -	spin_lock_irq(&events_lock); +	spin_lock_irqsave(&events_lock, flags);  	list_del_rcu(&ws->entry); -	spin_unlock_irq(&events_lock); +	spin_unlock_irqrestore(&events_lock, flags);  	synchronize_rcu();  }  EXPORT_SYMBOL_GPL(wakeup_source_remove); @@ -138,8 +183,10 @@ EXPORT_SYMBOL_GPL(wakeup_source_register);   */  void wakeup_source_unregister(struct wakeup_source *ws)  { -	wakeup_source_remove(ws); -	wakeup_source_destroy(ws); +	if (ws) { +		wakeup_source_remove(ws); +		wakeup_source_destroy(ws); +	}  }  EXPORT_SYMBOL_GPL(wakeup_source_unregister); @@ -228,22 +275,59 @@ int device_wakeup_disable(struct device *dev)  EXPORT_SYMBOL_GPL(device_wakeup_disable);  /** + * device_set_wakeup_capable - Set/reset device wakeup capability flag. + * @dev: Device to handle. + * @capable: Whether or not @dev is capable of waking up the system from sleep. + * + * If @capable is set, set the @dev's power.can_wakeup flag and add its + * wakeup-related attributes to sysfs.  Otherwise, unset the @dev's + * power.can_wakeup flag and remove its wakeup-related attributes from sysfs. + * + * This function may sleep and it can't be called from any context where + * sleeping is not allowed. + */ +void device_set_wakeup_capable(struct device *dev, bool capable) +{ +	if (!!dev->power.can_wakeup == !!capable) +		return; + +	if (device_is_registered(dev) && !list_empty(&dev->power.entry)) { +		if (capable) { +			if (wakeup_sysfs_add(dev)) +				return; +		} else { +			wakeup_sysfs_remove(dev); +		} +	} +	dev->power.can_wakeup = capable; +} +EXPORT_SYMBOL_GPL(device_set_wakeup_capable); + +/**   * device_init_wakeup - Device wakeup initialization.   * @dev: Device to handle.   * @enable: Whether or not to enable @dev as a wakeup device.   *   * By default, most devices should leave wakeup disabled.  The exceptions are   * devices that everyone expects to be wakeup sources: keyboards, power buttons, - * possibly network interfaces, etc. + * possibly network interfaces, etc.  Also, devices that don't generate their + * own wakeup requests but merely forward requests from one bus to another + * (like PCI bridges) should have wakeup enabled by default.   */  int device_init_wakeup(struct device *dev, bool enable)  {  	int ret = 0; +	if (!dev) +		return -EINVAL; +  	if (enable) {  		device_set_wakeup_capable(dev, true);  		ret = device_wakeup_enable(dev);  	} else { +		if (dev->power.can_wakeup) +			device_wakeup_disable(dev); +  		device_set_wakeup_capable(dev, false);  	} @@ -302,12 +386,39 @@ EXPORT_SYMBOL_GPL(device_set_wakeup_enable);   */  static void wakeup_source_activate(struct wakeup_source *ws)  { +	unsigned int cec; + +	/* +	 * active wakeup source should bring the system +	 * out of PM_SUSPEND_FREEZE state +	 */ +	freeze_wake(); +  	ws->active = true;  	ws->active_count++; -	ws->timer_expires = jiffies;  	ws->last_time = ktime_get(); +	if (ws->autosleep_enabled) +		ws->start_prevent_time = ws->last_time; -	atomic_inc(&events_in_progress); +	/* Increment the counter of events in progress. */ +	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);  }  /** @@ -324,9 +435,11 @@ void __pm_stay_awake(struct wakeup_source *ws)  		return;  	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; +  	spin_unlock_irqrestore(&ws->lock, flags);  }  EXPORT_SYMBOL_GPL(__pm_stay_awake); @@ -355,6 +468,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. @@ -365,6 +489,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; @@ -391,17 +516,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);  	/* -	 * event_count has to be incremented before events_in_progress is -	 * modified, so that the callers of pm_check_wakeup_events() and -	 * pm_save_wakeup_count() don't see the old value of event_count and -	 * events_in_progress equal to zero at the same time. +	 * Increment the counter of registered wakeup events and decrement the +	 * couter of wakeup events in progress simultaneously.  	 */ -	atomic_inc(&event_count); -	smp_mb__before_atomic_dec(); -	atomic_dec(&events_in_progress); +	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);  }  /** @@ -450,11 +581,24 @@ EXPORT_SYMBOL_GPL(pm_relax);   * pm_wakeup_timer_fn - Delayed finalization of a wakeup event.   * @data: Address of the wakeup source object associated with the event source.   * - * Call __pm_relax() for the wakeup source whose address is stored in @data. + * Call wakeup_source_deactivate() for the wakeup source whose address is stored + * in @data if it is currently active and its timer has not been canceled and + * the expiration time of the timer is not in future.   */  static void pm_wakeup_timer_fn(unsigned long data)  { -	__pm_relax((struct wakeup_source *)data); +	struct wakeup_source *ws = (struct wakeup_source *)data; +	unsigned long flags; + +	spin_lock_irqsave(&ws->lock, flags); + +	if (ws->active && ws->timer_expires +	    && time_after_eq(jiffies, ws->timer_expires)) { +		wakeup_source_deactivate(ws); +		ws->expire_count++; +	} + +	spin_unlock_irqrestore(&ws->lock, flags);  }  /** @@ -479,9 +623,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); @@ -492,7 +634,7 @@ void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec)  	if (!expires)  		expires = 1; -	if (time_after(expires, ws->timer_expires)) { +	if (!ws->timer_expires || time_after(expires, ws->timer_expires)) {  		mod_timer(&ws->timer, expires);  		ws->timer_expires = expires;  	} @@ -523,75 +665,97 @@ 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) +void pm_print_active_wakeup_sources(void)  { -	unsigned long flags;  	struct wakeup_source *ws; +	int active = 0; +	struct wakeup_source *last_activity_ws = NULL;  	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); +		if (ws->active) { +			pr_info("active wakeup source: %s\n", ws->name); +			active = 1; +		} else if (!active && +			   (!last_activity_ws || +			    ktime_to_ns(ws->last_time) > +			    ktime_to_ns(last_activity_ws->last_time))) { +			last_activity_ws = ws; +		}  	} + +	if (!active && last_activity_ws) +		pr_info("last active wakeup source: %s\n", +			last_activity_ws->name);  	rcu_read_unlock();  } +EXPORT_SYMBOL_GPL(pm_print_active_wakeup_sources);  /** - * 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; +		unsigned int cnt, inpr; + +		split_counters(&cnt, &inpr); +		ret = (cnt != saved_count || inpr > 0); +		events_check_enabled = !ret;  	}  	spin_unlock_irqrestore(&events_lock, flags); -	if (!ret) -		pm_wakeup_update_hit_counts(); + +	if (ret) { +		pr_info("PM: Wakeup pending, aborting suspend\n"); +		pm_print_active_wakeup_sources(); +	} +  	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)  { -	bool ret; +	unsigned int cnt, inpr; -	if (capable(CAP_SYS_ADMIN)) -		events_check_enabled = false; +	if (block) { +		DEFINE_WAIT(wait); -	while (atomic_read(&events_in_progress) && !signal_pending(current)) { -		pm_wakeup_update_hit_counts(); -		schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT)); +		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);  	} -	ret = !atomic_read(&events_in_progress); -	*count = atomic_read(&event_count); -	return ret; +	split_counters(&cnt, &inpr); +	*count = cnt; +	return !inpr;  }  /** @@ -600,25 +764,53 @@ bool pm_get_wakeup_count(unsigned int *count)   *   * If @count is equal to the current number of registered wakeup events and the   * current number of wakeup events being processed is zero, store @count as the - * old number of registered wakeup events to be used by pm_check_wakeup_events() - * and return true.  Otherwise return false. + * old number of registered wakeup events for pm_check_wakeup_events(), enable + * wakeup events detection and return 'true'.  Otherwise disable wakeup events + * detection and return 'false'.   */  bool pm_save_wakeup_count(unsigned int count)  { -	bool ret = false; +	unsigned int cnt, inpr; +	unsigned long flags; -	spin_lock_irq(&events_lock); -	if (count == (unsigned int)atomic_read(&event_count) -	    && !atomic_read(&events_in_progress)) { +	events_check_enabled = false; +	spin_lock_irqsave(&events_lock, flags); +	split_counters(&cnt, &inpr); +	if (cnt == count && inpr == 0) {  		saved_count = count;  		events_check_enabled = true; -		ret = true;  	} -	spin_unlock_irq(&events_lock); -	if (!ret) -		pm_wakeup_update_hit_counts(); -	return ret; +	spin_unlock_irqrestore(&events_lock, flags); +	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; @@ -635,27 +827,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); @@ -670,8 +872,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)  | 
