@@ -142,11 +142,13 @@ extern void arch_suspend_disable_irqs(void);
extern void arch_suspend_enable_irqs(void);
extern int pm_suspend(suspend_state_t state);
+extern void pm_suspend_delay(void);
#else /* !CONFIG_SUSPEND */
#define suspend_valid_only_mem NULL
static inline void suspend_set_ops(struct platform_suspend_ops *ops) {}
static inline int pm_suspend(suspend_state_t state) { return -ENOSYS; }
+static inlint void pm_suspend_delay(void) {}
#endif /* !CONFIG_SUSPEND */
/* struct pbe is used for creating lists of pages that should be restored
@@ -46,6 +46,69 @@ bool valid_state(suspend_state_t state)
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}
+/*
+ * Devices that process potential wake-up events report each
+ * wake-up events by pm_suspend_delay();
+ * This ensures that suspend won't happen for a "little while"
+ * so the event has a chance to get to user-space.
+ * pm_suspend calls wait_for_blockers to wait the required
+ * "little while" and to check for signals.
+ * A process that requests a suspend should arrange (via
+ * fcntl(F_GETOWN)) to get signalled whenever a wake-up event
+ * is queued for user-space. This will ensure that if a suspend
+ * is requested at much the same time as a wakeup event arrives, either
+ * the suspend will be interrupted, or it will complete quickly.
+ *
+ * The "little while" is a heuristic to avoid having to explicitly
+ * track every event through the kernel with associated locking and unlocking.
+ * It should be more than the longest time it can take between an interrupt
+ * occurring and the corresponding event being queued to userspace
+ * (and the accompanying kill_fasync call).
+ * This duration is configurable at boot time, has a lower limit of 2
+ * jiffies and an upper limit of 10 seconds. It defaults to the minimum.
+ */
+static unsigned long little_while_jiffies = 2;
+static int __init setup_suspend_block_delay(char *str)
+{
+ unsigned long msec;
+ if (sscanf(str, "%lu", &msec) != 1)
+ return 1;
+ if (msec > 10000)
+ msec = 10000;
+ little_while_jiffies = msecs_to_jiffies(msec);
+ if (little_while_jiffies < 2)
+ little_while_jiffies = 2;
+ return 1;
+}
+__setup("suspend_block_delay=", setup_suspend_block_delay);
+
+static unsigned long next_little_while;
+void pm_suspend_delay()
+{
+ unsigned long then = jiffies + little_while_jiffies;
+
+ if (then != next_little_while)
+ next_little_while = then;
+}
+EXPORT_SYMBOL_GPL(pm_suspend_delay);
+
+static int wait_for_blockers(void)
+{
+ unsigned long timeout;
+
+ if (time_after(jiffies, next_little_while))
+ return 0;
+ timeout = next_little_while - jiffies;
+ if (timeout > msecs_to_jiffies(10000))
+ /* jiffy wrap */
+ return 0;
+
+ while (timeout && !signal_pending(current))
+ timeout = schedule_timeout_interruptible(timeout);
+ if (signal_pending(current))
+ return -EINTR;
+ return 0;
+}
/**
* suspend_valid_only_mem - generic memory-only valid callback
*
@@ -89,6 +152,10 @@ static int suspend_prepare(void)
if (error)
goto Finish;
+ error = wait_for_blockers();
+ if (error)
+ goto Finish;
+
error = usermodehelper_disable();
if (error)
goto Finish;