power: fix lcd resume taking long time with an ongoing file copy
author黄涛 <huangtao@rock-chips.com>
Wed, 28 Dec 2011 06:43:33 +0000 (14:43 +0800)
committer黄涛 <huangtao@rock-chips.com>
Wed, 28 Dec 2011 06:46:00 +0000 (14:46 +0800)
port from msm:
This fixes the issue where LCD takes a long time to come back up
since the execution of backlight on and late_resume works by the
suspend worker thread is delayed due to one (or more) of the
sys_sync calls in early_suspend and suspend paths taking a long
time (sometimes 15sec or more) for the below reported scenario(s):

Scenario 1 (copy with usb connected):
1. plug usb
2. adb shell
3. busybox cp /sdcard/file1 /sdcard/file2 (copy >= 100MB file1
   in sdcard/emmc to file2 in sdcard/emmc)
4. press end key to suspend
5. press end key again and it takes a long time for LCD to come
   back up

Scenario 2 (background copy):
1. plug usb
2. adb shell
3. busybox cp /sdcard/file1 /sdcard/file2 & (copy >= 100MB file1
   in sdcard/emmc to file2 in sdcard/emmc)
4. disconnect usb
5. press end key to suspend
6. press end key again and it takes a long time for LCD to come
   back up

A more common form of Scenario 2 is for the user to just use the
copy function on the UI to copy large file(s).

We address this by moving sys_sync calls to a separate workqueue
and having a timeout polling based mechanism to bail out of suspend
in case of user invoking a wakeup event (like end key press) while
we are waiting for the sys_sync completion at the synchronization
point in suspend worker thread context.

kernel/power/Kconfig
kernel/power/earlysuspend.c
kernel/power/power.h
kernel/power/process.c
kernel/power/suspend.c
kernel/power/wakelock.c

index b90fb99fe45f9c99417cb68f149dca412d5598ac..d50bb90e1c1f9728b82ea49cf2d9d4cc4afbcdd8 100644 (file)
@@ -301,3 +301,8 @@ config SUSPEND_TIME
          Prints the time spent in suspend in the kernel log, and
          keeps statistics on the time spent in suspend in
          /sys/kernel/debug/suspend_time
+
+config SUSPEND_SYNC_WORKQUEUE
+       bool "Suspend sync in workqueue"
+       depends on WAKELOCK
+       default y
index b15f02eba45cb7cc2d5f2bb6854a94124f6967cb..ba5e9763a8a3ae4c891330949b9d3461e9f61b59 100644 (file)
@@ -103,10 +103,14 @@ static void early_suspend(struct work_struct *work)
        }
        mutex_unlock(&early_suspend_lock);
 
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+       suspend_sys_sync_queue();
+#else
        if (debug_mask & DEBUG_SUSPEND)
                pr_info("early_suspend: sync\n");
 
        sys_sync();
+#endif
 abort:
        spin_lock_irqsave(&state_lock, irqflags);
        if (state == SUSPEND_REQUESTED_AND_SUSPENDED)
index b6b9006480ffd0e76d2a0e72071b0369c7cc6ee9..ae075550a9673c3d06e6b4f074a9cb04e883000b 100644 (file)
@@ -253,6 +253,11 @@ extern struct wake_lock main_wake_lock;
 extern suspend_state_t requested_suspend_state;
 #endif
 
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+extern void suspend_sys_sync_queue(void);
+extern int suspend_sys_sync_wait(void);
+#endif
+
 #ifdef CONFIG_USER_WAKELOCK
 ssize_t wake_lock_show(struct kobject *kobj, struct kobj_attribute *attr,
                        char *buf);
index dd7d22d2c0ff76943ad5e6e6bed01b22344d00d5..134eb87d0a5d7c5f77e7fbaeafec33fbcbcb8ce0 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/delay.h>
 #include <linux/workqueue.h>
 #include <linux/wakelock.h>
+#include "power.h"
 
 /* 
  * Timeout for stopping processes
@@ -159,6 +160,12 @@ int freeze_processes(void)
                goto Exit;
        printk("done.\n");
 
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+       error = suspend_sys_sync_wait();
+       if (error)
+               goto Exit;
+#endif
+
        printk("Freezing remaining freezable tasks ... ");
        error = try_to_freeze_tasks(false);
        if (error)
index 63774df522bf20ce5d96b00f1d9a414f2774c214..16e28b1e2a39343ce24ed9ed598f64149a1a24b9 100644 (file)
@@ -276,9 +276,13 @@ int enter_state(suspend_state_t state)
        if (!mutex_trylock(&pm_mutex))
                return -EBUSY;
 
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+       suspend_sys_sync_queue();
+#else
        printk(KERN_INFO "PM: Syncing filesystems ... ");
        sys_sync();
        printk("done.\n");
+#endif
 
        pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
        error = suspend_prepare();
index ffba72597e0540c461bc79a4e77325081119e1ee..a79defae30b66e2feb1f276ae631938887e8379c 100644 (file)
@@ -44,6 +44,12 @@ static DEFINE_SPINLOCK(list_lock);
 static LIST_HEAD(inactive_locks);
 static struct list_head active_wake_locks[WAKE_LOCK_TYPE_COUNT];
 static int current_event_num;
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+static int suspend_sys_sync_count;
+static DEFINE_SPINLOCK(suspend_sys_sync_lock);
+static struct workqueue_struct *suspend_sys_sync_work_queue;
+static DECLARE_COMPLETION(suspend_sys_sync_comp);
+#endif
 struct workqueue_struct *suspend_work_queue;
 struct wake_lock main_wake_lock;
 suspend_state_t requested_suspend_state = PM_SUSPEND_MEM;
@@ -266,6 +272,72 @@ long has_wake_lock(int type)
        return ret;
 }
 
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+static void suspend_sys_sync(struct work_struct *work)
+{
+       if (debug_mask & DEBUG_SUSPEND)
+               pr_info("PM: Syncing filesystems...\n");
+
+       sys_sync();
+
+       if (debug_mask & DEBUG_SUSPEND)
+               pr_info("sync done.\n");
+
+       spin_lock(&suspend_sys_sync_lock);
+       suspend_sys_sync_count--;
+       spin_unlock(&suspend_sys_sync_lock);
+}
+static DECLARE_WORK(suspend_sys_sync_work, suspend_sys_sync);
+
+void suspend_sys_sync_queue(void)
+{
+       int ret;
+
+       spin_lock(&suspend_sys_sync_lock);
+       ret = queue_work(suspend_sys_sync_work_queue, &suspend_sys_sync_work);
+       if (ret)
+               suspend_sys_sync_count++;
+       spin_unlock(&suspend_sys_sync_lock);
+}
+
+static bool suspend_sys_sync_abort;
+static void suspend_sys_sync_handler(unsigned long);
+static DEFINE_TIMER(suspend_sys_sync_timer, suspend_sys_sync_handler, 0, 0);
+/* value should be less then half of input event wake lock timeout value
+ * which is currently set to 5*HZ (see drivers/input/evdev.c)
+ */
+#define SUSPEND_SYS_SYNC_TIMEOUT (HZ/4)
+static void suspend_sys_sync_handler(unsigned long arg)
+{
+       if (suspend_sys_sync_count == 0) {
+               complete(&suspend_sys_sync_comp);
+       } else if (has_wake_lock(WAKE_LOCK_SUSPEND)) {
+               suspend_sys_sync_abort = true;
+               complete(&suspend_sys_sync_comp);
+       } else {
+               mod_timer(&suspend_sys_sync_timer, jiffies +
+                               SUSPEND_SYS_SYNC_TIMEOUT);
+       }
+}
+
+int suspend_sys_sync_wait(void)
+{
+       suspend_sys_sync_abort = false;
+
+       if (suspend_sys_sync_count != 0) {
+               mod_timer(&suspend_sys_sync_timer, jiffies +
+                               SUSPEND_SYS_SYNC_TIMEOUT);
+               wait_for_completion(&suspend_sys_sync_comp);
+       }
+       if (suspend_sys_sync_abort) {
+               pr_info("suspend aborted....while waiting for sys_sync\n");
+               return -EAGAIN;
+       }
+
+       return 0;
+}
+#endif /* CONFIG_SUSPEND_SYNC_WORKQUEUE */
+
 static void suspend_backoff(void)
 {
        pr_info("suspend: too many immediate wakeups, back off\n");
@@ -286,7 +358,11 @@ static void suspend(struct work_struct *work)
        }
 
        entry_event_num = current_event_num;
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+       suspend_sys_sync_queue();
+#else
        sys_sync();
+#endif
        if (debug_mask & DEBUG_SUSPEND)
                pr_info("suspend: enter suspend\n");
        getnstimeofday(&ts_entry);
@@ -594,6 +670,16 @@ static int __init wakelocks_init(void)
                goto err_platform_driver_register;
        }
 
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+       INIT_COMPLETION(suspend_sys_sync_comp);
+       suspend_sys_sync_work_queue =
+               create_singlethread_workqueue("suspend_sys_sync");
+       if (suspend_sys_sync_work_queue == NULL) {
+               ret = -ENOMEM;
+               goto err_suspend_sys_sync_work_queue;
+       }
+#endif
+
        suspend_work_queue = create_singlethread_workqueue("suspend");
        if (suspend_work_queue == NULL) {
                ret = -ENOMEM;
@@ -607,6 +693,10 @@ static int __init wakelocks_init(void)
        return 0;
 
 err_suspend_work_queue:
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+       destroy_workqueue(suspend_sys_sync_work_queue);
+err_suspend_sys_sync_work_queue:
+#endif
        platform_driver_unregister(&power_driver);
 err_platform_driver_register:
        platform_device_unregister(&power_device);
@@ -626,6 +716,9 @@ static void  __exit wakelocks_exit(void)
        remove_proc_entry("wakelocks", NULL);
 #endif
        destroy_workqueue(suspend_work_queue);
+#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE
+       destroy_workqueue(suspend_sys_sync_work_queue);
+#endif
        platform_driver_unregister(&power_driver);
        platform_device_unregister(&power_device);
        wake_lock_destroy(&suspend_backoff_lock);