[PATCH 8/9] PM: suspend_block: Add timeout support.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add suspend_block_timeout to block suspend for a limited time.
Signed-off-by: Arve Hjønnevåg <arve@xxxxxxxxxxx>--- Documentation/power/suspend-blockers.txt |   10 ++ include/linux/suspend_blocker.h          |    7 +- kernel/power/suspend_blocker.c           |  235 +++++++++++++++++++++++++---- 3 files changed, 218 insertions(+), 34 deletions(-)
diff --git a/Documentation/power/suspend-blockers.txt b/Documentation/power/suspend-blockers.txtindex d81e20e..fd9f932 100644--- a/Documentation/power/suspend-blockers.txt+++ b/Documentation/power/suspend-blockers.txt@@ -93,6 +93,16 @@ if (list_empty(&state->pending_work)) else 	suspend_block(&state->suspend_blocker); +A driver can also call suspend_block_timeout to release the suspend_blocker+after a delay:+	suspend_block_timeout(&state->suspend_blocker, HZ);++This works whether the suspend_blocker is already active or not. It is useful if+the driver woke up other parts of the system that do not use suspend_blockers+but still need to run. Avoid this when possible, since it will waste power+if the timeout is long or may fail to finish needed work if the timeout is+short. Calling suspend_block or suspend_unblock will cancel the timeout.+ User-space API ============== diff --git a/include/linux/suspend_blocker.h b/include/linux/suspend_blocker.hindex 3bb8a6a..e3ae136 100755--- a/include/linux/suspend_blocker.h+++ b/include/linux/suspend_blocker.h@@ -22,8 +22,9 @@ /**  * struct suspend_blocker - the basic suspend_blocker structure  * @link:	List entry for active or inactive list.- * @flags:	Tracks initialized, active and stats state.+ * @flags:	Tracks initialized, active, stats and autoexpire state.  * @name:	Name used for debugging.+ * @expires:	Time, in jiffies, to unblock suspend.  * @count:	Number of times this blocker has been deacivated  * @wakeup_count: Number of times this blocker was the first to block suspend  *		after resume.@@ -44,9 +45,11 @@ struct suspend_blocker { 	struct list_head    link; 	int                 flags; 	const char         *name;+	unsigned long       expires; #ifdef CONFIG_SUSPEND_BLOCKER_STATS 	struct { 		int             count;+		int             expire_count; 		int             wakeup_count; 		ktime_t         total_time; 		ktime_t         prevent_suspend_time;@@ -62,6 +65,7 @@ struct suspend_blocker { void suspend_blocker_init(struct suspend_blocker *blocker, const char *name); void suspend_blocker_destroy(struct suspend_blocker *blocker); void suspend_block(struct suspend_blocker *blocker);+void suspend_block_timeout(struct suspend_blocker *blocker, long timeout); void suspend_unblock(struct suspend_blocker *blocker); bool suspend_blocker_is_active(struct suspend_blocker *blocker); bool suspend_is_blocked(void);@@ -72,6 +76,7 @@ static inline void suspend_blocker_init(struct suspend_blocker *blocker, 					const char *name) {} static inline void suspend_blocker_destroy(struct suspend_blocker *blocker) {} static inline void suspend_block(struct suspend_blocker *blocker) {}+static inline void suspend_block_timeout(struct suspend_blocker *bl, long t) {} static inline void suspend_unblock(struct suspend_blocker *blocker) {} static inline bool suspend_blocker_is_active(struct suspend_blocker *bl) 								{ return 0; }diff --git a/kernel/power/suspend_blocker.c b/kernel/power/suspend_blocker.cindex ab4d8d5..fbe4903 100644--- a/kernel/power/suspend_blocker.c+++ b/kernel/power/suspend_blocker.c@@ -27,13 +27,15 @@ enum { 	DEBUG_USER_STATE = 1U << 2, 	DEBUG_SUSPEND = 1U << 3, 	DEBUG_SUSPEND_BLOCKER = 1U << 4,+	DEBUG_EXPIRE = 1U << 5, }; static int debug_mask = DEBUG_EXIT_SUSPEND | DEBUG_WAKEUP | DEBUG_USER_STATE; module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);  #define SB_INITIALIZED            (1U << 8) #define SB_ACTIVE                 (1U << 9)-#define SB_PREVENTING_SUSPEND     (1U << 10)+#define SB_AUTO_EXPIRE            (1U << 10)+#define SB_PREVENTING_SUSPEND     (1U << 11)  static DEFINE_SPINLOCK(list_lock); static DEFINE_SPINLOCK(state_lock);@@ -64,20 +66,53 @@ static struct suspend_blocker deleted_suspend_blockers; static ktime_t last_sleep_time_update; static bool wait_for_wakeup; +static bool stats_get_expired_time(struct suspend_blocker *blocker,+				   ktime_t *expire_time)+{+	struct timespec ts;+	struct timespec kt;+	struct timespec tomono;+	struct timespec delta;+	unsigned long seq;+	long timeout;++	if (!(blocker->flags & SB_AUTO_EXPIRE))+		return false;+	do {+		seq = read_seqbegin(&xtime_lock);+		timeout = blocker->expires - jiffies;+		if (timeout > 0)+			return false;+		kt = current_kernel_time();+		tomono = wall_to_monotonic;+	} while (read_seqretry(&xtime_lock, seq));+	jiffies_to_timespec(-timeout, &delta);+	set_normalized_timespec(&ts, kt.tv_sec + tomono.tv_sec - delta.tv_sec,+				kt.tv_nsec + tomono.tv_nsec - delta.tv_nsec);+	*expire_time = timespec_to_ktime(ts);+	return true;+}+ static int print_blocker_stat(struct seq_file *m, 			      struct suspend_blocker *blocker) { 	int lock_count = blocker->stat.count;+	int expire_count = blocker->stat.expire_count; 	ktime_t active_time = ktime_set(0, 0); 	ktime_t total_time = blocker->stat.total_time; 	ktime_t max_time = blocker->stat.max_time; 	ktime_t prevent_suspend_time = blocker->stat.prevent_suspend_time; 	if (blocker->flags & SB_ACTIVE) { 		ktime_t now, add_time;-		now = ktime_get();+		bool expired = stats_get_expired_time(blocker, &now);+		if (!expired)+			now = ktime_get(); 		add_time = ktime_sub(now, blocker->stat.last_time); 		lock_count++;-		active_time = add_time;+		if (!expired)+			active_time = add_time;+		else+			expire_count++; 		total_time = ktime_add(total_time, add_time); 		if (blocker->flags & SB_PREVENTING_SUSPEND) 			prevent_suspend_time = ktime_add(prevent_suspend_time,@@ -86,9 +121,10 @@ static int print_blocker_stat(struct seq_file *m, 			max_time = add_time; 	} -	return seq_printf(m, "\"%s\"\t%d\t%d\t%lld\t%lld\t%lld\t%lld\t%lld\n",-		       blocker->name, lock_count, blocker->stat.wakeup_count,-		       ktime_to_ns(active_time), ktime_to_ns(total_time),+	return seq_printf(m, "\"%s\"\t%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\t"+		       "%lld\n", blocker->name, lock_count, expire_count,+		       blocker->stat.wakeup_count, ktime_to_ns(active_time),+		       ktime_to_ns(total_time), 		       ktime_to_ns(prevent_suspend_time), ktime_to_ns(max_time), 		       ktime_to_ns(blocker->stat.last_time)); }@@ -99,7 +135,7 @@ static int suspend_blocker_stats_show(struct seq_file *m, void *unused) 	unsigned long irqflags; 	struct suspend_blocker *blocker; -	seq_puts(m, "name\tcount\twake_count\tactive_since"+	seq_puts(m, "name\tcount\texpire_count\twake_count\tactive_since" 		 "\ttotal_time\tsleep_time\tmax_time\tlast_change\n"); 	spin_lock_irqsave(&list_lock, irqflags); 	list_for_each_entry(blocker, &inactive_blockers, link)@@ -113,6 +149,7 @@ static int suspend_blocker_stats_show(struct seq_file *m, void *unused) static void suspend_blocker_stat_init_locked(struct suspend_blocker *blocker) { 	blocker->stat.count = 0;+	blocker->stat.expire_count = 0; 	blocker->stat.wakeup_count = 0; 	blocker->stat.total_time = ktime_set(0, 0); 	blocker->stat.prevent_suspend_time = ktime_set(0, 0);@@ -125,6 +162,7 @@ static void suspend_blocker_stat_destroy_locked(struct suspend_blocker *bl) 	if (!bl->stat.count) 		return; 	deleted_suspend_blockers.stat.count += bl->stat.count;+	deleted_suspend_blockers.stat.expire_count += bl->stat.expire_count; 	deleted_suspend_blockers.stat.total_time = ktime_add( 		deleted_suspend_blockers.stat.total_time, bl->stat.total_time); 	deleted_suspend_blockers.stat.prevent_suspend_time = ktime_add(@@ -134,14 +172,20 @@ static void suspend_blocker_stat_destroy_locked(struct suspend_blocker *bl) 		deleted_suspend_blockers.stat.max_time, bl->stat.max_time); } -static void suspend_unblock_stat_locked(struct suspend_blocker *blocker)+static void suspend_unblock_stat_locked(struct suspend_blocker *blocker,+					bool expired) { 	ktime_t duration; 	ktime_t now; 	if (!(blocker->flags & SB_ACTIVE)) 		return;-	now = ktime_get();+	if (stats_get_expired_time(blocker, &now))+		expired = true;+	else+		now = ktime_get(); 	blocker->stat.count++;+	if (expired)+		blocker->stat.expire_count++; 	duration = ktime_sub(now, blocker->stat.last_time); 	blocker->stat.total_time = 		ktime_add(blocker->stat.total_time, duration);@@ -164,6 +208,12 @@ static void suspend_block_stat_locked(struct suspend_blocker *blocker) 		wait_for_wakeup = false; 		blocker->stat.wakeup_count++; 	}+	if ((blocker->flags & SB_AUTO_EXPIRE) &&+	    time_is_before_eq_jiffies(blocker->expires)) {+		suspend_unblock_stat_locked(blocker, false);+		blocker->stat.last_time = ktime_get();+	}+ 	if (!(blocker->flags & SB_ACTIVE)) 		blocker->stat.last_time = ktime_get(); }@@ -171,17 +221,22 @@ static void suspend_block_stat_locked(struct suspend_blocker *blocker) static void update_sleep_wait_stats_locked(bool done) { 	struct suspend_blocker *blocker;-	ktime_t now, elapsed, add;+	ktime_t now, etime, elapsed, add;+	bool expired;  	now = ktime_get(); 	elapsed = ktime_sub(now, last_sleep_time_update); 	list_for_each_entry(blocker, &active_blockers, link) {+		expired = stats_get_expired_time(blocker, &etime); 		if (blocker->flags & SB_PREVENTING_SUSPEND) {-			add = elapsed;+			if (expired)+				add = ktime_sub(etime, last_sleep_time_update);+			else+				add = elapsed; 			blocker->stat.prevent_suspend_time = ktime_add( 				blocker->stat.prevent_suspend_time, add); 		}-		if (done)+		if (done || expired) 			blocker->flags &= ~SB_PREVENTING_SUSPEND; 		else 			blocker->flags |= SB_PREVENTING_SUSPEND;@@ -197,8 +252,8 @@ static inline void suspend_blocker_stat_destroy_locked( 					struct suspend_blocker *blocker) {} static inline void suspend_block_stat_locked( 					struct suspend_blocker *blocker) {}-static inline void suspend_unblock_stat_locked(-					struct suspend_blocker *blocker) {}+static inline void suspend_unblock_stat_locked(struct suspend_blocker *blocker,+					   bool expired) {} static inline void update_sleep_wait_stats_locked(bool done) {}  static int suspend_blocker_stats_show(struct seq_file *m, void *unused)@@ -218,12 +273,50 @@ static int suspend_blocker_stats_show(struct seq_file *m, void *unused)  #endif +static void expire_suspend_blocker(struct suspend_blocker *blocker)+{+	suspend_unblock_stat_locked(blocker, true);+	blocker->flags &= ~(SB_ACTIVE | SB_AUTO_EXPIRE);+	list_del(&blocker->link);+	list_add(&blocker->link, &inactive_blockers);+	if (debug_mask & (DEBUG_SUSPEND_BLOCKER | DEBUG_EXPIRE))+		pr_info("expired suspend blocker %s\n", blocker->name);+}+ static void print_active_blockers_locked(void) { 	struct suspend_blocker *blocker; -	list_for_each_entry(blocker, &active_blockers, link)-		pr_info("active suspend blocker %s\n", blocker->name);+	list_for_each_entry(blocker, &active_blockers, link) {+		if (blocker->flags & SB_AUTO_EXPIRE) {+			long timeout = blocker->expires - jiffies;+			if (timeout <= 0)+				pr_info("suspend blocker %s, expired\n",+					blocker->name);+			else+				pr_info("active suspend blocker %s, time left "+					"%ld\n", blocker->name, timeout);+		} else+			pr_info("active suspend blocker %s\n", blocker->name);+	}+}++static long max_suspend_blocker_timeout_locked(void)+{+	struct suspend_blocker *blocker, *n;+	long max_timeout = 0;++	list_for_each_entry_safe(blocker, n, &active_blockers, link) {+		if (blocker->flags & SB_AUTO_EXPIRE) {+			long timeout = blocker->expires - jiffies;+			if (timeout <= 0)+				expire_suspend_blocker(blocker);+			else if (timeout > max_timeout)+				max_timeout = timeout;+		} else+			return -1;+	}+	return max_timeout; }  /**@@ -237,9 +330,14 @@ static void print_active_blockers_locked(void)  */ bool suspend_is_blocked(void) {+	long ret;+	unsigned long irqflags; 	if (!enable_suspend_blockers) 		return 0;-	return !list_empty(&active_blockers);+	spin_lock_irqsave(&list_lock, irqflags);+	ret = !!max_suspend_blocker_timeout_locked();+	spin_unlock_irqrestore(&list_lock, irqflags);+	return ret; }  static void suspend_worker(struct work_struct *work)@@ -264,14 +362,49 @@ static void suspend_worker(struct work_struct *work) 	if (current_event_num == entry_event_num) { 		if (debug_mask & DEBUG_SUSPEND) 			pr_info("suspend: pm_suspend returned with no event\n");-		suspend_block(&unknown_wakeup);-		suspend_unblock(&unknown_wakeup);+		suspend_block_timeout(&unknown_wakeup, HZ / 2); 	} abort: 	enable_suspend_blockers = false; } static DECLARE_WORK(suspend_work, suspend_worker); +static void expire_suspend_blockers(unsigned long data)+{+	long timeout;+	unsigned long irqflags;+	if (debug_mask & DEBUG_EXPIRE)+		pr_info("expire_suspend_blockers: start\n");+	spin_lock_irqsave(&list_lock, irqflags);+	if (debug_mask & DEBUG_SUSPEND)+		print_active_blockers_locked();+	timeout = max_suspend_blocker_timeout_locked();+	if (debug_mask & DEBUG_EXPIRE)+		pr_info("expire_suspend_blockers: done, timeout %ld\n",+			timeout);+	if (timeout == 0)+		queue_work(suspend_work_queue, &suspend_work);+	spin_unlock_irqrestore(&list_lock, irqflags);+}+static DEFINE_TIMER(expire_timer, expire_suspend_blockers, 0, 0);++static void update_suspend(struct suspend_blocker *blocker, long max_timeout)+{+	if (max_timeout > 0) {+		if (debug_mask & DEBUG_EXPIRE)+			pr_info("suspend_blocker: %s, start expire timer, "+				"%ld\n", blocker->name, max_timeout);+		mod_timer(&expire_timer, jiffies + max_timeout);+	} else {+		if (del_timer(&expire_timer))+			if (debug_mask & DEBUG_EXPIRE)+				pr_info("suspend_blocker: %s, stop expire "+					"timer\n", blocker->name);+		if (max_timeout == 0)+			queue_work(suspend_work_queue, &suspend_work);+	}+}+ static int suspend_block_suspend(struct sys_device *dev, pm_message_t state) { 	int ret = suspend_is_blocked() ? -EAGAIN : 0;@@ -334,17 +467,14 @@ void suspend_blocker_destroy(struct suspend_blocker *blocker) 	suspend_blocker_stat_destroy_locked(blocker); 	blocker->flags &= ~SB_INITIALIZED; 	list_del(&blocker->link);-	if ((blocker->flags & SB_ACTIVE) && list_empty(&active_blockers))-		queue_work(suspend_work_queue, &suspend_work);+	if (blocker->flags & SB_ACTIVE)+		update_suspend(blocker, max_suspend_blocker_timeout_locked()); 	spin_unlock_irqrestore(&list_lock, irqflags); } EXPORT_SYMBOL(suspend_blocker_destroy); -/**- * suspend_block() - Block suspend- * @blocker:	The suspend blocker to use- */-void suspend_block(struct suspend_blocker *blocker)+static void __suspend_block(struct suspend_blocker *blocker, long timeout,+			    bool has_timeout) { 	unsigned long irqflags; @@ -355,20 +485,56 @@ void suspend_block(struct suspend_blocker *blocker) 	suspend_block_stat_locked(blocker); 	blocker->flags |= SB_ACTIVE; 	list_del(&blocker->link);-	if (debug_mask & DEBUG_SUSPEND_BLOCKER)-		pr_info("suspend_block: %s\n", blocker->name);-	list_add(&blocker->link, &active_blockers);+	if (has_timeout) {+		if (debug_mask & DEBUG_SUSPEND_BLOCKER)+			pr_info("suspend_block: %s, timeout %ld.%03lu\n",+				blocker->name, timeout / HZ,+				(timeout % HZ) * MSEC_PER_SEC / HZ);+		blocker->expires = jiffies + timeout;+		blocker->flags |= SB_AUTO_EXPIRE;+		list_add_tail(&blocker->link, &active_blockers);+	} else {+		if (debug_mask & DEBUG_SUSPEND_BLOCKER)+			pr_info("suspend_block: %s\n", blocker->name);+		blocker->expires = LONG_MAX;+		blocker->flags &= ~SB_AUTO_EXPIRE;+		/* Add to head so suspend_is_blocked only has to examine */+		/* one entry */+		list_add(&blocker->link, &active_blockers);+	}  	current_event_num++; 	if (blocker == &main_suspend_blocker) 		update_sleep_wait_stats_locked(true); 	else if (!suspend_blocker_is_active(&main_suspend_blocker)) 		update_sleep_wait_stats_locked(false);+	update_suspend(blocker, has_timeout ?+			max_suspend_blocker_timeout_locked() : -1); 	spin_unlock_irqrestore(&list_lock, irqflags); }++/**+ * suspend_block() - Block suspend+ * @blocker:	The suspend blocker to use+ */+void suspend_block(struct suspend_blocker *blocker)+{+	__suspend_block(blocker, 0, false);+} EXPORT_SYMBOL(suspend_block);  /**+ * suspend_block_timeout() - Block suspend for a limited time+ * @blocker:	The suspend blocker to use.+ * @timeout:	Timeout in jiffies before the suspend blocker auto-unblock+ */+void suspend_block_timeout(struct suspend_blocker *blocker, long timeout)+{+	__suspend_block(blocker, timeout, true);+}+EXPORT_SYMBOL(suspend_block_timeout);++/**  * suspend_unblock() - Unblock suspend  * @blocker:	The suspend blocker to unblock.  *@@ -383,16 +549,15 @@ void suspend_unblock(struct suspend_blocker *blocker)  	spin_lock_irqsave(&list_lock, irqflags); -	suspend_unblock_stat_locked(blocker);+	suspend_unblock_stat_locked(blocker, false);  	if (debug_mask & DEBUG_SUSPEND_BLOCKER) 		pr_info("suspend_unblock: %s\n", blocker->name);+	blocker->flags &= ~(SB_ACTIVE | SB_AUTO_EXPIRE); 	list_del(&blocker->link); 	list_add(&blocker->link, &inactive_blockers); -	if ((blocker->flags & SB_ACTIVE) && list_empty(&active_blockers))-		queue_work(suspend_work_queue, &suspend_work);-	blocker->flags &= ~(SB_ACTIVE);+	update_suspend(blocker, max_suspend_blocker_timeout_locked()); 	if (blocker == &main_suspend_blocker) { 		if (debug_mask & DEBUG_SUSPEND) 			print_active_blockers_locked();@@ -407,6 +572,10 @@ EXPORT_SYMBOL(suspend_unblock);  * @blocker:	The suspend blocker to check.  *  * Returns true if the suspend_blocker is currently active.+ *+ * If the suspend_blocker has a timeout, it does not check the timeout, but if+ * the timeout had already expired when it was checked elsewhere this function+ * will return false.  */ bool suspend_blocker_is_active(struct suspend_blocker *blocker) {-- 1.6.1
_______________________________________________linux-pm mailing listlinux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx://lists.linux-foundation.org/mailman/listinfo/linux-pm


[Index of Archives]     [Linux ACPI]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [CPU Freq]     [Kernel Newbies]     [Fedora Kernel]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux