// SPDX-License-Identifier: GPL-2.0-only
/*
* kernel/power/main.c - PM subsystem core functionality.
*
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Lab
*/
#include <linux/acpi.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/pm-trace.h>
#include <linux/workqueue.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/suspend.h>
#include <linux/syscalls.h>
#include <linux/pm_runtime.h>
#include <linux/atomic.h>
#include <linux/wait.h>
#include "power.h"
#ifdef CONFIG_PM_SLEEP
/*
* The following functions are used by the suspend/hibernate code to temporarily
* change gfp_allowed_mask in order to avoid using I/O during memory allocations
* while devices are suspended. To avoid races with the suspend/hibernate code,
* they should always be called with system_transition_mutex held
* (gfp_allowed_mask also should only be modified with system_transition_mutex
* held, unless the suspend/hibernate code is guaranteed not to run in parallel
* with that modification).
*/
static unsigned int saved_gfp_count;
static gfp_t saved_gfp_mask;
void pm_restore_gfp_mask(void)
{
WARN_ON(!mutex_is_locked(&system_transition_mutex));
if (!saved_gfp_count || --saved_gfp_count)
return;
gfp_allowed_mask = saved_gfp_mask;
saved_gfp_mask = 0;
pm_pr_dbg("GFP mask restored\n");
}
void pm_restrict_gfp_mask(void)
{
WARN_ON(!mutex_is_locked(&system_transition_mutex));
if (saved_gfp_count++) {
WARN_ON((saved_gfp_mask & ~(__GFP_IO | __GFP_FS)) != gfp_allowed_mask);
return;
}
saved_gfp_mask = gfp_allowed_mask;
gfp_allowed_mask &= ~(__GFP_IO | __GFP_FS);
pm_pr_dbg("GFP mask restricted\n");
}
unsigned int lock_system_sleep(void)
{
unsigned int flags = current->flags;
current->flags |= PF_NOFREEZE;
mutex_lock(&system_transition_mutex);
return flags;
}
EXPORT_SYMBOL_GPL(lock_system_sleep);
void unlock_system_sleep(unsigned int flags)
{
if (!(flags & PF_NOFREEZE))
current->flags &= ~PF_NOFREEZE;
mutex_unlock(&system_transition_mutex);
}
EXPORT_SYMBOL_GPL(unlock_system_sleep);
void ksys_sync_helper(void)
{
ktime_t start;
long elapsed_msecs;
start = ktime_get();
ksys_sync();
elapsed_msecs = ktime_to_ms(ktime_sub(ktime_get(), start));
pr_info("Filesystems sync: %ld.%03ld seconds\n",
elapsed_msecs / MSEC_PER_SEC, elapsed_msecs % MSEC_PER_SEC);
}
EXPORT_SYMBOL_GPL(ksys_sync_helper);
#if defined(CONFIG_SUSPEND) || defined(CONFIG_HIBERNATION)
/* Wakeup events handling resolution while syncing file systems in jiffies */
#define PM_FS_SYNC_WAKEUP_RESOLUTION 5
static atomic_t pm_fs_sync_count = ATOMIC_INIT(0);
static struct workqueue_struct *pm_fs_sync_wq;
static DECLARE_WAIT_QUEUE_HEAD(pm_fs_sync_wait);
static bool pm_fs_sync_completed(void)
{
return atomic_read(&pm_fs_sync_count) == 0;
}
static void pm_fs_sync_work_fn(struct work_struct *work)
{
ksys_sync_helper();
if (atomic_dec_and_test(&pm_fs_sync_count))
wake_up(&pm_fs_sync_wait);
}
static DECLARE_WORK(pm_fs_sync_work, pm_fs_sync_work_fn);
/**
* pm_sleep_fs_sync() - Sync file systems in an interruptible way
*
* Return: 0 on successful file system sync, or -EBUSY if the file system sync
* was aborted.
*/
int pm_sleep_fs_sync(void)
{
pm_wakeup_clear(0);
/*
* Take back-to-back sleeps into account by queuing a subsequent fs sync
* only if the previous fs sync is running or is not queued. Multiple fs
* syncs increase the likelihood of saving the latest files immediately
* before sleep.
*/
if (!work_pending(&pm_fs_sync_work)) {
atomic_inc(&pm_fs_sync_count);
queue_work(pm_fs_sync_wq, &pm_fs_sync_work);
}
while (!pm_fs_sync_completed()) {
if (pm_wakeup_pending())
return -EBUSY;
wait_event_timeout(pm_fs_sync_wait,