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