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_block.h | 7 + kernel/power/suspend_block.c | 232 +++++++++++++++++++++++++----- 3 files changed, 216 insertions(+), 33 deletions(-) diff --git a/Documentation/power/suspend-blockers.txt b/Documentation/power/suspend-blockers.txtindex 35bc713..2781e05 100644--- a/Documentation/power/suspend-blockers.txt+++ b/Documentation/power/suspend-blockers.txt@@ -74,6 +74,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_block.h b/include/linux/suspend_block.hindex 31225c5..9cea772 100755--- a/include/linux/suspend_block.h+++ b/include/linux/suspend_block.h@@ -27,9 +27,11 @@ struct suspend_blocker { struct list_head link; int flags; const char *name;+ unsigned long expires; #ifdef CONFIG_SUSPEND_BLOCK_STAT struct { int count;+ int expire_count; int wakeup_count; ktime_t total_time; ktime_t prevent_suspend_time;@@ -45,9 +47,13 @@ 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); /* is_blocking_suspend 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 is_blocking_suspend(struct suspend_blocker *blocker); @@ -65,6 +71,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 is_blocking_suspend(struct suspend_blocker *bl) { return 0; } static inline bool suspend_is_blocked(void) { return 0; }diff --git a/kernel/power/suspend_block.c b/kernel/power/suspend_block.cindex 78d5880..9414455 100644--- a/kernel/power/suspend_block.c+++ b/kernel/power/suspend_block.c@@ -29,13 +29,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);@@ -53,19 +55,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(char *buf, 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,@@ -74,9 +110,10 @@ static int print_blocker_stat(char *buf, struct suspend_blocker *blocker) max_time = add_time; } - return sprintf(buf, "\"%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 sprintf(buf, "\"%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)); }@@ -92,7 +129,7 @@ static int suspend_blocker_stats_read_proc(char *page, char **start, off_t off, spin_lock_irqsave(&list_lock, irqflags); - p += sprintf(p, "name\tcount\twake_count\tactive_since"+ p += sprintf(p, "name\tcount\texpire_count\twake_count\tactive_since" "\ttotal_time\tsleep_time\tmax_time\tlast_change\n"); list_for_each_entry(blocker, &inactive_blockers, link) { p += print_blocker_stat(p, blocker);@@ -115,6 +152,7 @@ static int suspend_blocker_stats_read_proc(char *page, char **start, off_t off, 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);@@ -127,6 +165,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(@@ -136,14 +175,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);@@ -166,6 +211,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(); }@@ -173,17 +224,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;@@ -199,26 +255,69 @@ 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) {} #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; } bool suspend_is_blocked(void) {+ long ret;+ unsigned long irqflags; if (WARN_ONCE(!enable_suspend_blockers, "ignoring suspend blockers\n")) 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)@@ -251,14 +350,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 = 0; } 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;@@ -318,17 +452,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; @@ -338,20 +469,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 (!is_blocking_suspend(&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. *@@ -363,16 +530,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();-- 1.6.1 _______________________________________________linux-pm mailing listlinux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx://lists.linux-foundation.org/mailman/listinfo/linux-pm