linux/kernel/power/autosleep.c
<<
>>
Prefs
   1/*
   2 * kernel/power/autosleep.c
   3 *
   4 * Opportunistic sleep support.
   5 *
   6 * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl>
   7 */
   8
   9#include <linux/device.h>
  10#include <linux/mutex.h>
  11#include <linux/pm_wakeup.h>
  12
  13#include "power.h"
  14
  15static suspend_state_t autosleep_state;
  16static struct workqueue_struct *autosleep_wq;
  17/*
  18 * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source
  19 * is active, otherwise a deadlock with try_to_suspend() is possible.
  20 * Alternatively mutex_lock_interruptible() can be used.  This will then fail
  21 * if an auto_sleep cycle tries to freeze processes.
  22 */
  23static DEFINE_MUTEX(autosleep_lock);
  24static struct wakeup_source *autosleep_ws;
  25
  26static void try_to_suspend(struct work_struct *work)
  27{
  28        unsigned int initial_count, final_count;
  29
  30        if (!pm_get_wakeup_count(&initial_count, true))
  31                goto out;
  32
  33        mutex_lock(&autosleep_lock);
  34
  35        if (!pm_save_wakeup_count(initial_count)) {
  36                mutex_unlock(&autosleep_lock);
  37                goto out;
  38        }
  39
  40        if (autosleep_state == PM_SUSPEND_ON) {
  41                mutex_unlock(&autosleep_lock);
  42                return;
  43        }
  44        if (autosleep_state >= PM_SUSPEND_MAX)
  45                hibernate();
  46        else
  47                pm_suspend(autosleep_state);
  48
  49        mutex_unlock(&autosleep_lock);
  50
  51        if (!pm_get_wakeup_count(&final_count, false))
  52                goto out;
  53
  54        /*
  55         * If the wakeup occured for an unknown reason, wait to prevent the
  56         * system from trying to suspend and waking up in a tight loop.
  57         */
  58        if (final_count == initial_count)
  59                schedule_timeout_uninterruptible(HZ / 2);
  60
  61 out:
  62        queue_up_suspend_work();
  63}
  64
  65static DECLARE_WORK(suspend_work, try_to_suspend);
  66
  67void queue_up_suspend_work(void)
  68{
  69        if (!work_pending(&suspend_work) && autosleep_state > PM_SUSPEND_ON)
  70                queue_work(autosleep_wq, &suspend_work);
  71}
  72
  73suspend_state_t pm_autosleep_state(void)
  74{
  75        return autosleep_state;
  76}
  77
  78int pm_autosleep_lock(void)
  79{
  80        return mutex_lock_interruptible(&autosleep_lock);
  81}
  82
  83void pm_autosleep_unlock(void)
  84{
  85        mutex_unlock(&autosleep_lock);
  86}
  87
  88int pm_autosleep_set_state(suspend_state_t state)
  89{
  90
  91#ifndef CONFIG_HIBERNATION
  92        if (state >= PM_SUSPEND_MAX)
  93                return -EINVAL;
  94#endif
  95
  96        __pm_stay_awake(autosleep_ws);
  97
  98        mutex_lock(&autosleep_lock);
  99
 100        autosleep_state = state;
 101
 102        __pm_relax(autosleep_ws);
 103
 104        if (state > PM_SUSPEND_ON) {
 105                pm_wakep_autosleep_enabled(true);
 106                queue_up_suspend_work();
 107        } else {
 108                pm_wakep_autosleep_enabled(false);
 109        }
 110
 111        mutex_unlock(&autosleep_lock);
 112        return 0;
 113}
 114
 115int __init pm_autosleep_init(void)
 116{
 117        autosleep_ws = wakeup_source_register("autosleep");
 118        if (!autosleep_ws)
 119                return -ENOMEM;
 120
 121        autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
 122        if (autosleep_wq)
 123                return 0;
 124
 125        wakeup_source_unregister(autosleep_ws);
 126        return -ENOMEM;
 127}
 128
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.