123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * kernel/power/autosleep.c
- *
- * Opportunistic sleep support.
- *
- * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl>
- */
- #include <linux/device.h>
- #include <linux/mutex.h>
- #include <linux/pm_wakeup.h>
- #include "power.h"
- static suspend_state_t autosleep_state;
- static struct workqueue_struct *autosleep_wq;
- /*
- * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source
- * is active, otherwise a deadlock with try_to_suspend() is possible.
- * Alternatively mutex_lock_interruptible() can be used. This will then fail
- * if an auto_sleep cycle tries to freeze processes.
- */
- static DEFINE_MUTEX(autosleep_lock);
- static struct wakeup_source *autosleep_ws;
- static void try_to_suspend(struct work_struct *work)
- {
- unsigned int initial_count, final_count;
- if (!pm_get_wakeup_count(&initial_count, true))
- goto out;
- mutex_lock(&autosleep_lock);
- if (!pm_save_wakeup_count(initial_count) ||
- system_state != SYSTEM_RUNNING) {
- mutex_unlock(&autosleep_lock);
- goto out;
- }
- if (autosleep_state == PM_SUSPEND_ON) {
- mutex_unlock(&autosleep_lock);
- return;
- }
- if (autosleep_state >= PM_SUSPEND_MAX)
- hibernate();
- else
- pm_suspend(autosleep_state);
- mutex_unlock(&autosleep_lock);
- if (!pm_get_wakeup_count(&final_count, false))
- goto out;
- /*
- * If the wakeup occured for an unknown reason, wait to prevent the
- * system from trying to suspend and waking up in a tight loop.
- */
- if (final_count == initial_count)
- schedule_timeout_uninterruptible(HZ / 2);
- out:
- queue_up_suspend_work();
- }
- static DECLARE_WORK(suspend_work, try_to_suspend);
- void queue_up_suspend_work(void)
- {
- if (autosleep_state > PM_SUSPEND_ON)
- queue_work(autosleep_wq, &suspend_work);
- }
- suspend_state_t pm_autosleep_state(void)
- {
- return autosleep_state;
- }
- int pm_autosleep_lock(void)
- {
- return mutex_lock_interruptible(&autosleep_lock);
- }
- void pm_autosleep_unlock(void)
- {
- mutex_unlock(&autosleep_lock);
- }
- int pm_autosleep_set_state(suspend_state_t state)
- {
- #ifndef CONFIG_HIBERNATION
- if (state >= PM_SUSPEND_MAX)
- return -EINVAL;
- #endif
- __pm_stay_awake(autosleep_ws);
- mutex_lock(&autosleep_lock);
- autosleep_state = state;
- __pm_relax(autosleep_ws);
- if (state > PM_SUSPEND_ON) {
- pm_wakep_autosleep_enabled(true);
- queue_up_suspend_work();
- } else {
- pm_wakep_autosleep_enabled(false);
- }
- mutex_unlock(&autosleep_lock);
- return 0;
- }
- int __init pm_autosleep_init(void)
- {
- autosleep_ws = wakeup_source_register("autosleep");
- if (!autosleep_ws)
- return -ENOMEM;
- autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
- if (autosleep_wq)
- return 0;
- wakeup_source_unregister(autosleep_ws);
- return -ENOMEM;
- }
|